%{
// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <stdio.h>
#include <iostream>
#define YY_DECL
#include <cantor/parser/parser.h>
using cantor::parser::SqlParser;
using cantor::parser::InsertStmt;
using cantor::parser::Node;
using cantor::parser::Assignment;
using cantor::parser::DispatchExpr;
using cantor::pb::ast::FuncType;
using cantor::parser::FuncExpr;
using cantor::parser::StmtNode;
using cantor::parser::String;
using cantor::parser::RowExpr;
using cantor::parser::RangeVar;
using cantor::pb::ast::PriorityEnum;
using cantor::parser::CreateTableStmt;
using cantor::parser::CreateViewStmt;
using cantor::parser::CommonTableExpr;
using cantor::parser::TableRef;

using namespace cantor::parser;
#include <cantor/parser/grammar/scanner.h>
#include <cantor/parser/grammar/grammar.h>
extern int sql_lex(YYSTYPE* yylval, YYLTYPE* yylloc, yyscan_t yyscanner, SqlParser* parser);
extern int sql_error(YYLTYPE* yylloc, yyscan_t yyscanner, SqlParser* parser, const char* s);
#define new_node(T) new(parser->arena.allocate(sizeof(T)))T()

%}

%defines
%name-prefix "sql_"
%error-verbose
%lex-param {yyscan_t yyscanner}
%lex-param {SqlParser* parser}
%parse-param {yyscan_t yyscanner}
%parse-param {SqlParser* parser}

%token-table
%pure-parser
%verbose

%union
{
    int integer;
    Node* item;  // item can be used as list/array
    StmtNode*       stmt;
    String          string;
    SelectStmtOpts* select_opts;
    GroupByClause*  group_by;
    OrderByClause*  order_by;
    LimitClause*    limit;
    Vector<int>*    int_list;
    PrivType*                 priv_type;
    Vector<PrivType*>*        priv_type_list;
    UserSpec*                 user_spec;
    Vector<UserSpec*>*        user_spec_list;
    NamespaceOption*          namespace_option;
    Vector<NamespaceOption*>* namespace_option_list;
    ColumnOption*             column_option;
    Vector<ColumnOption*>*    column_option_list;
    ColumnDef*                column_def;
    Vector<ColumnDef*>*       column_def_list;
    WhenClauseCase*           when_clause_case;
    Vector<WhenClauseCase*>*  when_clause_case_list;
    WhenClause*               when_clause;
    Constraint*               constraint;
    TableDefElement*          table_def_element;
    Vector<TableDefElement*>* table_def_element_list;
    PartitionRange*           partition_range;
    Vector<PartitionRange*>*  partition_range_list;
    DatabaseOption*           database_option;
    Vector<DatabaseOption*>*  database_option_list;
    Assignment*               assignment;
    Vector<Assignment*>*      assignment_list;
    IndexHint*                index_hint;
    Vector<IndexHint*>*       index_hint_list;
    SelectField*              select_field;
    Vector<SelectField*>*     select_field_list;
    ByItem*                   by_item;
    Vector<ByItem*>*          by_item_list;
    TableOption*              table_option;
    Vector<TableOption*>*     table_option_list;
    Vector<String>*           string_list;
    PartitionOption*          partition_option;
    Vector<PartitionOption*>* partition_option_list;
    AlterTableSpec*           alter_spec;
    Vector<AlterTableSpec*>*  alter_spec_list;
    FieldItem*                field_item;
    Vector<FieldItem*>*       field_item_list;
    RowExpr*                  row_expr;
    AuthOption*               auth_option;
    UserIdentity*             user_identity;
    PrivLevel*                priv_level;
    JoinNode*                 Join_node;
    IntConstantExpr*          int_const_expr;
    StringConstantExpr*       string_const_expr;
    Vector<IntConstantExpr*>* const_int_expr_list;
    ConstantExpr*             cexpr;
    Vector<ConstantExpr*>*    cexpr_list;
    TypeStmt*                 type_name;
    Vector<TypeStmt*>*        type_name_list;
    Indirection*              indirection;
    Vector<Indirection*>*     indirection_list;
    RangeVar*                 range_var;
    Vector<RangeVar*>*        range_var_list;
    DispatchExpr*                 expr;
    Vector<DispatchExpr*>*        expr_list;
    DictPair*                 dict_pair;
    Vector<DictPair*>*        dict_pair_list;
    MapPair*                  map_pair;
    Vector<MapPair*>*         map_pair_list;
    EnumElement*              enum_element;
    Vector<EnumElement*>*     enum_element_list;
    cantor::pb::ast::TypeScope          type_scope;
    cantor::pb::ast::TimeStampPrecision        timestamp_precision;
    TableRef*                 table_ref;
    FieldsClause*             fields_clause;
    LinesClause*              lines_clause;
    WithClause*               with_clause;
    PragmaSetStmt*            pragma_set_stmt;
    VarAssign*                var_assign;
    TablePartitionOption*     table_partition_option;
    IndexOption*              index_option;
    IndexLockOpt*             index_lock_opt;
    cantor::pb::ast::IndexAlgoType      index_type;
    AlgorithmOpt*             algorighm_opt;
    cantor::pb::ast::IndexDistributeType  index_distribute_type;
    cantor::pb::ast::OnDuplicateKeyHandle on_duplicate_key_handle;
    cantor::pb::ast::SelectLock           select_lock;
    NamedType*                          named_type;
    Vector<NamedType*>*                 named_type_list;
    WhereClause*                        where_clause;
    HavingClause*                       having_clause;
    CtorFuncExpr*                       ctor_func_expr;
    TableName*                          table_name;
    Vector<TableName*>*                 table_name_list;
    TypeNameUri*                        type_name_uri;
    ColumnName*                         column_name;
    Vector<ColumnName*>*                column_name_list;
}

%token 
/* The following tokens belong to ReservedKeyword. */
    ADD
    ALL
    ALTER
    ANALYZE
    AND
    AS
    ASC
    BETWEEN
    BIGINT
    BINARY
    BY
    CASCADE
    CASE
    CHANGE
    CHARACTER
    STRING
    CHECK
    COLLATE
    COLUMN
    CONSTRAINT
    CONVERT
    CREATE
    CROSS
    CURRENT_USER
    DATABASE
    DATABASES
    DECIMAL
    DEFAULT
    DELAYED
    DELETE
    DESC
    DESCRIBE
    DISTINCT
    DISTINCTROW
    DIV
    DOUBLE
    DROP
    DUAL
    ELSE
    ENCLOSED
    ESCAPED
    EXISTS
    EXPLAIN
    FALSE
    FLOAT
    FOR
    FORCE
    FOREIGN
    FROM
    FULL
    FULLTEXT
    GENERATED
    GRANT
    GROUP
    HAVING
    HIGH_PRIORITY
    IF
    IGNORE
    IN
    INDEX
    INFILE
    INNER
    INTERVAL
    DURATION
    INTO
    IS
    INSERT
    INTEGER
    INT
    INT8
    INT16
    INT3
    INT32
    INT64
    INT128
    UINT
    UINT8
    UINT16
    UINT3
    UINT32
    UINT64
    UINT128
    FP8
    FP16
    FP32
    FP64
    JOIN
    KEY
    KEYS
    KILL
    LEFT
    LIKE
    EXACT_LIKE
    LIMIT
    LINES
    LOAD
    LOCK
    LONGBLOB
    LONGTEXT
    LOW_PRIORITY
    MATCH
    MAXVALUE
    MEDIUMBLOB
    MEDIUMINT
    MEDIUMTEXT
    MOD
    NAMESPACE
    NOT
    NO_WRITE_TO_BINLOG
    NULLX
    NUMERIC
    ON
    OPTION
    OR
    ORDER
    OUTER
    PACK_KEYS
    PARTITION
    PRECISION
    PROCEDURE
    SHARD_ROW_ID_BITS
    RANGE
    READ
    REAL
    REFERENCES
    REGEXP
    RENAME
    REPLACE
    MERGE
    RESTRICT
    REVOKE
    RIGHT
    RLIKE
    SELECT
    SET
    PRAGMA
    SHOW
    SMALLINT
    SQL
    SQL_CALC_FOUND_ROWS
    STARTING
    STRAIGHT_JOIN
    TABLE
    STORED
    TERMINATED
    OPTIONALLY
    THEN
    TINYBLOB
    TINYINT
    TINYTEXT
    TO
    TRIGGER
    TRUE
    UNIQUE
    UNION
    UNLOCK
    UNSIGNED
    UPDATE
    USAGE
    USE
    USING
    VALUES
    LONG
    _BINARY
    _UTF8MB4
    VIRTUAL
    WHEN
    WHERE
    WRITE
    WITH
    XOR
    ZEROFILL
    NATURAL
    
%token<string>
    /* The following tokens belong to ReservedKeyword. */
    BOTH
    CURRENT_DATE
    CURRENT_TIME
    DAY_HOUR
    DAY_MICROSECOND
    DAY_MINUTE
    DAY_SECOND
    HOUR_MICROSECOND
    HOUR_MINUTE
    HOUR_SECOND
    LEADING
    MINUTE_MICROSECOND
    MINUTE_SECOND
    PRIMARY
    SECOND_MICROSECOND
    TRAILING
    YEAR_MONTH

%token<string>
    /* The following tokens belong to UnReservedKeyword. */
    ARRAY
    TYPE
    ACTION
    AFTER
    AGAINST
    ALWAYS
    ALGORITHM
    ANY
    ASCII
    AUTO_INCREMENT
    AUTOEXTEND_SIZE
    AVG_ROW_LENGTH
    AVG
    BEGINX
    TXN
    WORK
    BINLOG
    BIT
    BOOLEAN
    BOOL
    BTREE
    BYTE
    CASCADED
    CHARSET
    CHECKSUM
    CLEANUP
    CLIENT
    COALESCE
    COLLATION
    COLUMNS
    COMMENT
    COMMIT
    COMMITTED
    COMPACT
    COMPRESSED
    COMPRESSION
    CONNECTION
    CONSISTENT
    COPY
    DAY
    DATA
    DATE
    DATETIME
    DEALLOCATE
    DEFINER
    DELAY_KEY_WRITE
    DISABLE
    DO
    DUPLICATE
    DYNAMIC
    ENABLE
    ENCRYPTION
    END
    ENGINE
    ENGINES
    ENGINE_ATTRIBUTE
    ENUM
    EVENT
    EVENTS
    ESCAPE
    EXCLUSIVE
    EXECUTE
    FIELDS
    FIRST
    LAST
    FIXED
    FILE_T
    FLUSH
    FORMAT
    FULL
    FUNCTION
    GRANTS
    HASH
    HOUR
    IDENTIFIED
    ISOLATION
    INDEXES
    INPLACE
    INSERT_METHOD
    INSTANT
    INVOKER
    JSON
    KEY_BLOCK_SIZE
    DYNAMIC_PARTITION_ATTR
    LANGUAGE
    LOCAL
    LESS
    LEVEL
    MASTER
    NANOSECOND
    MICROSECOND
    MILLISECOND
    MINUTE
    MODE
    MODIFY
    MONTH
    MAX_ROWS
    MAX_CONNECTIONS_PER_HOUR
    MAX_QUERIES_PER_HOUR
    MAX_UPDATES_PER_HOUR
    MAX_USER_CONNECTIONS
    MERGE
    MIN_ROWS
    NAMES
    NATIONAL
    NO
    NONE
    OFFSET
    ONLY
    PASSWORD
    PARTITIONS
    PLUGINS
    PREPARE
    PRIVILEGES
    PROCESS
    PROCESSLIST
    PROFILES
    PROXY
    QUARTER
    QUERY
    QUERIES
    QUICK
    RECOVER
    RESTORE 
    REDUNDANT
    RELOAD
    REPEATABLE
    REPLICATION
    ROLLBACK
    ROLLUP
    ROUTINE
    ROW
    ROW_COUNT
    ROW_FORMAT
    SECOND
    SECONDARY_ENGINE_ATTRIBUTE
    SECURITY
    SEPARATOR
    SERIALIZABLE
    SESSION
    SHARE
    SHARED
    SIGNED
    SLAVE
    SNAPSHOT
    SQL_CACHE
    SQL_NO_CACHE
    SHUTDOWN
    START
    STATS_PERSISTENT
    STATS_AUTO_RECALC
    STATS_SAMPLE_PAGES
    STATUS
    SUPER
    SOME
    SWAP
    GLOBAL
    TABLES
    TABLESPACE
    TEMPORARY
    TEMPTABLE
    TEXT
    THAN
    TIME
    TIMESTAMP
    MAP
    TUPLE
    STRUCT
    PROTO
    TRACE
    TRANSACTION
    TRIGGERS
    TRUNCATE
    UNCOMMITTED
    UNKNOWN
    USER
    UNDEFINED
    VALUE
    VARIABLES
    VIEW
    VECTOR 
    WARNINGS
    WEEK
    YEAR
    HLL
    BITMAP
    TDIGEST
    LEARNER

    /* The following tokens belong to builtin functions. */
    ADDDATE
    BIT_AND
    BIT_OR
    BIT_XOR
    CAST
    COUNT
    CURDATE
    CURTIME
    CURRENT_TIMESTAMP
    DATE_ADD
    DATE_SUB
    EXTRACT
    GROUP_CONCAT
    LOCALTIME
    LOCALTIMESTAMP
    MAX
    MID
    MIN
    NOW
    UTC_TIMESTAMP
    POSITION
    SESSION_USER
    STD
    STDDEV
    STDDEV_POP
    STDDEV_SAMP
    SUBDATE
    SUBSTR
    SUBSTRING
    TIMESTAMPADD
    TIMESTAMPDIFF
    SUM
    SYSDATE
    SYSTEM_USER
    TRIM
    VARIANCE
    VAR_POP
    VAR_SAMP
    USER_AGG

%token EQ_OP COLON ASSIGN_OP  MOD_OP  GE_OP  GT_OP LE_OP LT_OP NE_OP AND_OP OR_OP NOT_OP LS_OP RS_OP CHINESE_DOT JS_OP JS_OP1
%token <string> IDENT
%token <expr>   DECIMAL_LIT PLACE_HOLDER_LIT
%token <int_const_expr> INTEGER_LIT
%token <string_const_expr> STRING_LIT

%type <string_const_expr> AlgorithmVal IndexLockVal
%type <timestamp_precision> TimestampPprecision DateTimePprecision
%type <enum_element> EnumElement
%type <enum_element_list> EnumElementList EnumElementListOptComma
%type <named_type_list> colid_type_list
%type <cexpr>  const_expr
%type <type_name> GenericType
                  SimpleTypename
                  NumericTypename
                  DateAndTimeTypename CharacterType TypeStmt
                  IntegerTypeStmt FloatTypeStmt

%type <type_scope> TypeScope
%type <type_name_list> type_list
%type <int_const_expr> EnumOpt
%type <dict_pair> DictPair OptDictPair
%type <dict_pair_list> OptDictPairListOptComma DictPairListOptComma DictPairList
%type <map_pair> MapPair
%type <map_pair_list> MapArgList MapArgListOptComma OptMapArgListOptComma
%type <ctor_func_expr> CtorFuncExpr
%type <expr_list> ArrayExprList ArrayExprListOptComma OptArrayExprListOptComma
%type <cexpr_list>
    opt_type_modifiers
    opt_const_expr_list_opt_comma
    const_expr_list_opt_comma
    const_expr_list

%type <indirection> indirection_el
%type <indirection_list> indirection_list indirection_list_opt

%type <const_int_expr_list> opt_array_bounds
%type <string> 
    AllIdent
    Fieldkey
    ColId
    type_name_token
    TableAsNameOpt 
    TableAsName 
    ConstraintKeywordOpt 
    IndexName 
    StringName 
    OptCharset 
    OptCollate
    DBName
    FunctionNameCurtime
    FunctionaNameCurdate
    FunctionaNameDateRelate
    FunctionNameDateArithMultiForms
    FunctionNameDateArith
    FunctionNameSubstring
    VarName
    AllIdentOrPrimary
    FieldAsNameOpt
    FieldAsName
    ShowDatabaseNameOpt
    ShowTableAliasOpt
    LinesTerminated
    Starting
    ResourceTag
    AuthString
    NSName
    NamespaceOpt
    OptTagOption



%type <integer> RowFormatOpt InsertMethodOpt
/// Expr
%type <expr> Expr ElseOpt
/////////////////////////////////////////////////////////////////////
/// RangeVar | const expr | function | RowExpr | SubqueryExpr
%type <expr> SimpleExpr
/////////////////////////////////////////////////////////////////////
//// current nullptr
%type <expr> ShowLikeOrWhereOpt
/////////////////////////////////////////////////////////////////////
/// function or constexpr
%type <expr> DefaultValue
/////////////////////////////////////////////////////////////////////
/// sub query or cte query
%type <expr> CompareSubqueryExpr SubSelect CommonTableExpr
/////////////////////////////////////////////////////////////////////
/// function
%type <expr> FunctionCall
             Operators
             PredicateOp
             BuildInFun
             SumExpr
             FunctionCallNonKeyword
             FunctionCallKeyword
//////////////////////////////////////////////////////////////////////
/// constant expr
%type <expr> NumLiteral
             SignedLiteral
             Literal
             LikeEscapeOpt
             TimestampUnit
             TimeUnit
             FuncDatetimePrecListOpt
             TrimDirection
             FulltextSearchModifierOpt
             IsolationLevel
             SeparatorOpt
//////////////////////////////////////////////////////////////////////
/// RowExpr
%type <expr> RowExprList
             RowExpr
//////////////////////////////////////////////////////////////////////
%type <having_clause> HavingClauseOptional
%type <where_clause> WhereClause WhereClauseOptional
%type <algorighm_opt> AlgorithmOpt
%type <index_type>  IndexType IndexTypeOpt
%type <index_lock_opt> IndexLockOpt
%type <index_option>  IndexOption
                      IndexOptionList
                      DropIndexOptNoneUsed
%type <table_partition_option> TablePartitionOpt
%type <var_assign> TransactionChar
                    VarAssignItem
%type <row_expr> ExprList
%type <field_item> FieldItem
%type <field_item_list> FieldItemList
%type <partition_option> PartitionOption
%type <partition_option_list> PartitionOptionList
                              PartitionOptionListOpt
%type <column_def> ColumnDef
%type <column_def_list> ColumnDefList
%type <column_option_list> ColumnOptionList
%type <column_option> ColumnOption
%type <namespace_option> NamespaceOption
%type <namespace_option_list> NamespaceOptionListOpt
%type <namespace_option_list> NamespaceOptionList
%type <table_option> TableOption
%type <table_option_list> TableOptionList
                          CreateTableOptionListOpt
%type <item>
    OptionalBraces


%type <with_clause> WithClause WithList
%type <lines_clause> Lines
%type <fields_clause> Fields
%type <table_ref> TableRefs
                  TableRef
                  TableFactor
                  JoinTable
                  EscapedTableRef
%type <alter_spec> AlterSpec
%type <alter_spec_list> AlterSpecList

%type <priv_type> PrivType
%type <priv_type_list> PrivTypeList
%type <user_spec> UserSpec
%type <user_spec_list> UserSpecList
%type <when_clause_case> WhenCase
%type <when_clause_case_list> WhenCaseList
%type <when_clause> WhenClause
%type <constraint> Constraint ConstraintElem
%type <table_def_element> TableElement
%type <table_def_element_list> TableElementList TableElementListOpt
%type <table_name> TableName ViewName
%type <column_name> ColumnName IndexColumnField
%type <column_name_list> IndexColumnList ColumnNameList ColumnNameListOpt ViewFieldList ColumnNameOrUserVarListOptWithBrackets
%type <table_name_list> TableNameList
%type <type_name_uri> TypeNameUri OptTypeNameUri
%type <range_var> RangeVar OptRangeVar
%type <range_var_list> RangeVarColumnNameList RangeVarColumnNameListOpt


%type <partition_range> PartitionRange
%type <partition_range_list> PartitionRangeList PartitionRangeListOpt
%type <database_option> DatabaseOption
%type <database_option_list> DatabaseOptionList DatabaseOptionListOpt


%type <priv_level> PrivLevel
%type <user_identity> Username
%type <auth_option> AuthOption

%type <stmt> 
    MultiStmt
    Statement
    InsertStmt
    InsertValues
    ValueList
    UpdateStmt
    ReplaceStmt
    DeleteStmt
    SelectStmtBasic
    SelectStmtFromDual
    SelectStmtFromTable
    SelectStmt
    ViewSelectStmt
    SelectStmtWithClause
    UnionSelect
    UnionClauseList
    UnionStmt
    TruncateStmt 
    ShowStmt
    ShowTargetFilterable
    ExplainStmt
    ExplainableStmt
    KillStmt
    CreateTypeStmt

%type <stmt> 
    CreateTableStmt
    CreateViewStmt
    DropTableStmt
    DropViewStmt
    RestoreTableStmt
    CreateDatabaseStmt
    DropDatabaseStmt
    StartTransactionStmt
    CommitTransactionStmt
    RollbackTransactionStmt
    PragmaSetStmt
    VarAssignList
    AlterTableStmt
    AlterViewStmt
    NewPrepareStmt
    ExecPrepareStmt
    DeallocPrepareStmt
    TransactionChars
    LoadDataStmt
    CreateNamespaceStmt
    DropNamespaceStmt
    AlterNamespaceStmt
    CreateUserStmt
    DropUserStmt
    AlterUserStmt
    GrantStmt
    RevokeStmt

%type <assignment> Assignment
%type <assignment_list> AssignmentList OnDuplicateKeyUpdate LoadDataSetSpecOpt

%type <integer>
    PriorityOpt 
    IgnoreOptional
    MergeOptional
    QuickOptional
    Order
    IndexHintType
    IndexHintScope
    JoinType
    IfNotExists
    IfExists
    OrReplace
    IsOrNot
    InOrNot
    AnyOrAll
    LikeOrNot
    RegexpOrNot
    BetweenOrNot
    SelectStmtCalcFoundRows
    SelectStmtSQLCache
    SelectStmtStraightJoin
    DistinctOpt
    DefaultFalseDistinctOpt
    DefaultTrueDistinctOpt
    BuggyDefaultFalseDistinctOpt
    GlobalScope
    OptFull
    UnionOpt
    CompareOp
    IgnoreLines
    LocalOpt
    ForceOrNot
    WithGrantOptionOpt
    SqlACL

%type <select_lock> SelectLockOpt
%type <on_duplicate_key_handle> DuplicateOpt
%type <index_distribute_type> GlobalOrLocal GlobalOrLocalOpt
%type <string_list> IndexNameList VarList PartitionNameListOpt PartitionNameList
%type <index_hint> IndexHint
%type <index_hint_list> IndexHintList IndexHintListOpt
%type <select_opts> SelectStmtOpts
%type <select_field> SelectField
%type <select_field_list> SelectFieldList
%type <group_by> GroupBy GroupByOptional
%type <order_by> OrderBy OrderByOptional
%type <by_item> ByItem;
%type <by_item_list> ByList;
%type <limit> LimitClause;

%nonassoc empty
%nonassoc lowerThanSetKeyword
%nonassoc SET
%nonassoc lowerThanKey
%nonassoc KEY
%nonassoc SQL_CACHE SQL_NO_CACHE

%left tableRefPriority
%left JOIN STRAIGHT_JOIN INNER CROSS LEFT RIGHT FULL NATURAL
%precedence ON USING

%left XOR OR
%left AND
%left EQ_OP NE_OP GE_OP GT_OP LE_OP LT_OP IS LIKE IN 
%left '|'
%left '&'
%left JS_OP1
%left LS_OP RS_OP JS_OP
%left '+' '-'
%left '*' '/' MOD_OP  MOD
%left '^'
%left COLLATE BINARY
%right '~' NEG NOT NOT_OP
%right '.'
%nonassoc '('
%nonassoc QUICK
%precedence lowerThanComma
%precedence ','

%%

// Parse Entrance
MultiStmt:
    Statement {
        parser->result.push_back($1);
    }
    | MultiStmt ';' Statement {
        parser->result.push_back($3);
    }
    | MultiStmt ';' {
        $$ = $1;
    }
    ;

Statement:
    InsertStmt
    | ReplaceStmt
    | UpdateStmt
    | DeleteStmt
    | TruncateStmt
    | CreateTableStmt
    | CreateViewStmt
    | SelectStmt
    | UnionStmt
    | DropTableStmt
    | DropViewStmt
    | RestoreTableStmt
    | CreateDatabaseStmt
    | DropDatabaseStmt
    | StartTransactionStmt
    | CommitTransactionStmt
    | RollbackTransactionStmt
    | PragmaSetStmt
    | ShowStmt
    | AlterTableStmt
    | AlterViewStmt
    | NewPrepareStmt
    | ExecPrepareStmt
    | DeallocPrepareStmt
    | ExplainStmt
    | KillStmt
    | LoadDataStmt
    | CreateNamespaceStmt
    | DropNamespaceStmt
    | AlterNamespaceStmt
    | CreateUserStmt
    | DropUserStmt
    | AlterUserStmt
    | GrantStmt
    | RevokeStmt
    | SelectStmtWithClause
    | CreateTypeStmt
    | TypeStmt
    {
        $$ = (StmtNode*)$1;
    }
    ;

InsertStmt:
    INSERT PriorityOpt IgnoreOptional MergeOptional IntoOpt TableName PartitionNameListOpt InsertValues OnDuplicateKeyUpdate {
        InsertStmt* insert = (InsertStmt*)$8;
        insert->priority = (PriorityEnum)$2;
        insert->is_ignore = (bool)$3;
        insert->is_merge = (bool)$4;
        insert->table_name = $6;
        set_vector(insert->partition_names, $7);
        set_vector(insert->on_duplicate, $9);
        insert->location = parser->get_location(@1, @$);
        $$ = insert;
    }
    ;

ReplaceStmt:
    REPLACE PriorityOpt IntoOpt TableName PartitionNameListOpt InsertValues {
        InsertStmt* insert = (InsertStmt*)$6;
        insert->priority = (PriorityEnum)$2;
        insert->is_replace = true;
        insert->table_name = $4;
        insert->location = parser->get_location(@1, @$);
        set_vector(insert->partition_names, $5);
        $$ = insert;
    }
    ;

PriorityOpt:
    {
        $$ = cantor::pb::ast::PE_NO_PRIORITY;
    }
    | LOW_PRIORITY {
        $$ = cantor::pb::ast::PE_LOW_PRIORITY;
    }
    | HIGH_PRIORITY {
        $$ = cantor::pb::ast::PE_HIGH_PRIORITY;
    }
    | DELAYED {
        $$ = cantor::pb::ast::PE_DELAYED_PRIORITY;
    }
    ;
IgnoreOptional:
    {
        $$ = false; // false
    }
    | IGNORE {
        $$ = true; //true;
    }
    ;
MergeOptional:
    {
        $$ = false; // false
    }
    | MERGE {
        $$ = true; //true;
    }
    ;
QuickOptional:
    %prec empty {
        $$ = false;
    }
    | QUICK {
        $$ = true;
    }
    ;
IntoOpt:
    {}
    | INTO
    ;

InsertValues:
    '(' ColumnNameListOpt ')' values_sym ValueList {
        auto* insert = (InsertStmt*)$5;
        insert->location = parser->get_location(@1, @$);
        set_vector(insert->columns, $2);
        $$ = insert;
    }
    | '(' ColumnNameListOpt ')' SelectStmt {
        auto* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        set_vector(insert->columns, $2);
        insert->subquery_stmt = (SelectStmt*)$4;
        $$ = insert;
    }
    | '(' ColumnNameListOpt ')' '(' SelectStmt ')' {
        auto* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        set_vector(insert->columns, $2);
        auto* select = (SelectStmt*)$5;
        select->is_in_braces = true;
        insert->subquery_stmt = select;
        $$ = insert;
    }
    | '(' ColumnNameListOpt ')' UnionStmt {
        auto* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        set_vector(insert->columns, $2);
        insert->subquery_stmt = (UnionStmt*)$4;
        $$ = insert;
    }
    | '(' SelectStmt ')' {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        SelectStmt* select = (SelectStmt*)$2;
        select->is_in_braces = true;
        insert->subquery_stmt = select;
        $$ = insert;
    }
    | SelectStmt {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        insert->subquery_stmt = (SelectStmt*)$1;
        $$ = insert;
    }
    | '(' UnionStmt ')' {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        UnionStmt* union_stmt = (UnionStmt*)$2;
        union_stmt->is_in_braces = true;
        insert->subquery_stmt = union_stmt;
        $$ = insert;
    }
    | UnionStmt {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        insert->subquery_stmt = (UnionStmt*)$1;
        $$ = insert;
    }
    | values_sym ValueList {
        InsertStmt* insert = (InsertStmt*)$2;
        insert->location = parser->get_location(@1, @$);
        $$ = insert;
    }
    | SET AssignmentList {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        auto row_expr = new_node(RowExpr);
        row_expr->location = parser->get_location(@2);
        for (int i = 0; i < $2->size(); i++) {
            Assignment* assign = (*$2)[i];
            insert->columns.push_back(assign->name, parser->arena);
            row_expr->sub_exprs.push_back(assign->expr, parser->arena);
        }
        insert->lists.push_back(row_expr, parser->arena);
        $$ = insert;
    }
    ;

values_sym:
    VALUE | VALUES
    ;
OnDuplicateKeyUpdate:
    {
        $$ = nullptr;    
    }
    | ON DUPLICATE KEY UPDATE AssignmentList {
        $$ = $5;
    }
    ;
AssignmentList:
    Assignment {
        $$ = append_vector<Assignment*>(nullptr, $1, parser->arena, 10);
    }
    | AssignmentList ',' Assignment {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;
Assignment:
    ColumnName EqAssign Expr {
        Assignment* assign = new_node(Assignment);
        assign->location = parser->get_location(@1, @$);
        assign->name = $1;
        assign->expr = $3;
        $$ = assign;
    }
    ;
EqAssign:
    EQ_OP | ASSIGN_OP
    ;

ValueList:
    '(' ExprList ')' {
        InsertStmt* insert = InsertStmt::New(parser->arena);
        insert->location = parser->get_location(@1, @$);
        insert->lists.push_back($2, parser->arena);
        $$ = insert;
    }
    | ValueList ',' '(' ExprList ')' {
        InsertStmt* insert = (InsertStmt*)$1;
        insert->location = parser->get_location(@1, @$);
        insert->lists.push_back($4, parser->arena);
        $$ = insert;
    }
    ;
// for IN_PREDICATE
RowExprList:
    RowExpr {
        auto row = new_node(RowExpr);
        row->sub_exprs.reserve(10, parser->arena);
        row->sub_exprs.push_back($1, parser->arena);
        $$ = row;
    }
    | RowExprList ',' RowExpr {
        auto row = (RowExpr*)$1;
        row->location = parser->get_location(@3);
        row->sub_exprs.push_back($3, parser->arena);
        $$ = row;
    }
    ;
RowExpr:
    '(' ExprList ',' Expr ')' {
        $2->sub_exprs.push_back($4, parser->arena);
        $2->location = parser->get_location(@1, @$);
        $$ = (RowExpr*)$2;
    }
    | ROW '(' ExprList ',' Expr ')' {
        $3->sub_exprs.push_back($5, parser->arena);
        $3->location = parser->get_location(@1, @$);
        $$ = (RowExpr*)$3;
    }
    ;
ExprList:
    Expr {
        auto row = new_node(RowExpr);
        row->location = parser->get_location(@1);
        row->sub_exprs.reserve(10, parser->arena);
        row->sub_exprs.push_back($1, parser->arena);
        $$ = row;
    } 
    | ExprList ',' Expr {
        auto row = (RowExpr*)$1;
        row->location = parser->get_location(@1, @$);
        row->sub_exprs.push_back($3, parser->arena);
        $$ = row;
    }
    ;
ColumnNameListOpt:
    /* empty */ {
        $$ = nullptr;
    }
    | ColumnNameList {
        $$ = $1;
    }
    ;

RangeVarColumnNameListOpt:
         /* empty */
            {
                $$ = nullptr;
            }
            | RangeVarColumnNameList {
                $$ = $1;
            }
            ;

RangeVarColumnNameList:
    RangeVar {
        $$ = append_vector<RangeVar*>(nullptr, $1, parser->arena, 10);
    }
    | RangeVarColumnNameList ',' RangeVar {
        $1->push_back($3, parser->arena);
        $$ = $1;
     }
    ;

ColumnNameList:
            ColumnName
            {
                $$ = append_vector<ColumnName*>(nullptr, $1, parser->arena, 10);
            }
            | ColumnNameList ',' ColumnName {
                $1->push_back($3, parser->arena);
                $$ = $1;
             }
            ;
RangeVar:
    ColumnName
    {
        auto* var = new_node(RangeVar);
        var->location = parser->get_location(@1);
        var->column = $1;
        $$ = var;
    }
    ;

OptRangeVar:
    {
        $$ = nullptr;
    }
    | RangeVar
    {
        $$ = $1;
    }
    ;

indirection_list_opt:
            { $$ = nullptr; }
            |  indirection_list
            { $$ = $1; }
            ;


indirection_list:
            indirection_el
            {
                $$ = append_vector<Indirection*>(nullptr, $1, parser->arena);
            }
            | indirection_list indirection_el
            {
                $$ = append_vector<Indirection*>($1, $2, parser->arena);
            }
            ;

indirection_el:
            '[' Expr ']'
            {
                auto node = new_node(IndirectionIndex);
                node->location = parser->get_location(@1, @$);
                node->index_expr = $2;
                $$ = node;
            }
            | '[' Expr COLON ']'
            {
                auto node = new_node(IndirectionSlice);
                node->location = parser->get_location(@1, @$);
                node->slice_low = $2;
                $$ = node;
            }
            | '[' COLON Expr ']'
            {
                auto node = new_node(IndirectionSlice);
                node->location = parser->get_location(@1, @$);
                node->slice_high = $3;
                $$ = node;
            }
            | '[' COLON ']'
            {
                auto node = new_node(IndirectionSlice);
                node->location = parser->get_location(@1, @$);
                $$ = node;
            }
            | '[' Expr COLON Expr ']'
            {
                auto node = new_node(IndirectionSlice);
                node->location = parser->get_location(@1, @$);
                node->slice_low = $2;
                node->slice_high = $4;
                $$ = node;
            }
            | '[' Expr COLON Expr COLON Expr ']'
            {
                auto node = new_node(IndirectionSlice);
                node->location = parser->get_location(@1, @$);
                node->slice_low = $2;
                node->slice_high = $4;
                node->slice_step = $6;
                $$ = node;
            }
		    ;

IndexColumnField:
    ColumnName {
        $$ = $1;
    }
    | ColumnName '(' INTEGER_LIT ')' {
        $$ = $1;
    }
    | IndexColumnField Order {
        $$ = $1;
    }
    ;

IndexColumnList:
    IndexColumnField {
        $$ = append_vector<ColumnName*>(nullptr, $1, parser->arena, 10);
    }
    | IndexColumnList ',' IndexColumnField {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

TableName:
        AllIdent
        {
            auto* name = new_node(TableName);
            name->table_name = $1;
            name->location = parser->get_location(@1);
            $$ = name;
        }
        | AllIdent '.' AllIdent
        {
                auto* name = new_node(TableName);
                name->location = parser->get_location(@1, @$);
                name->table_name = $3;
                name->db_name = $1;
                $$ = name;
        }
        | AllIdent '.' AllIdent '.' AllIdent
        {
                auto* name = new_node(TableName);
                name->location = parser->get_location(@1, @$);
                name->table_name = $5;
                name->db_name = $3;
                name->ns_name = $1;
                $$ = name;
        }
        ;

ColumnName:
        AllIdent
        {
            auto* name = new_node(ColumnName);
            name->column_name = $1;
            name->location = parser->get_location(@1);
            $$ = name;
        }
        | AllIdent '.' AllIdent
        {
                auto* name = new_node(ColumnName);
                name->location = parser->get_location(@1, @$);
                name->table_name = $1;
                name->column_name = $3;
                $$ = name;
        }
        | AllIdent '.' AllIdent '.' AllIdent
        {
                auto* name = new_node(ColumnName);
                name->location = parser->get_location(@1, @$);
                name->column_name = $5;
                name->table_name = $3;
                name->db_name = $1;
                $$ = name;
        }
        | AllIdent '.' AllIdent '.' AllIdent '.' AllIdent
        {
                auto* name = new_node(ColumnName);
                name->location = parser->get_location(@1, @$);
                name->column_name = $7;
                name->table_name = $5;
                name->db_name = $3;
                name->ns_name = $1;
                $$ = name;
        }
        ;

OptTypeNameUri:
            {
                $$ = nullptr;
            }
            | TypeNameUri
            {
                $$ = $1;
            }
TypeNameUri:
            AllIdent
            {
                auto* name = new_node(TypeNameUri);
                name->type_name = $1;
                name->location = parser->get_location(@1);
                $$ = name;
            }
            | AllIdent '.' AllIdent
            {
                auto* name = new_node(TypeNameUri);
                name->location = parser->get_location(@1, @$);
                name->ns_name = $1;
                name->type_name = $3;
                $$ = name;
            }
            | AllIdent '.' AllIdent '.' AllIdent
            {
                    auto* name = new_node(TypeNameUri);
                    name->location = parser->get_location(@1, @$);
                    name->ns_name = $1;
                    name->db_name = $3;
                    name->type_name = $5;
                    $$ = name;
            }
            | AllIdent '.' AllIdent '.' AllIdent '.' AllIdent
            {
                    auto* name = new_node(TypeNameUri);
                    name->location = parser->get_location(@1, @$);
                    name->ns_name = $1;
                    name->db_name = $3;
                    name->table_name = $5;
                    name->type_name = $7;
                    $$ = name;
            }
            ;
ViewName:
    TableName
    ;

TableNameList:
    TableName
    {
        $$ = append_vector<TableName*>(nullptr, $1, parser->arena, 10);
    }
    | TableNameList ',' TableName
    {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

UpdateStmt:
    UPDATE PriorityOpt IgnoreOptional TableRef SET AssignmentList WhereClauseOptional OrderByOptional LimitClause {
        UpdateStmt* update = new_node(UpdateStmt);
        update->location = parser->get_location(@1, @$);
        update->priority = (PriorityEnum)$2;
        update->is_ignore = $3;
        update->table_refs = $4;
        set_vector(update->set_list, $6);
        update->where = $7;
        update->order = $8;
        update->limit = $9;
        $$ = update;
    }
    | UPDATE PriorityOpt IgnoreOptional TableRefs SET AssignmentList WhereClauseOptional {
        UpdateStmt* update = new_node(UpdateStmt);
        update->location = parser->get_location(@1, @$);
        update->priority = (PriorityEnum)$2;
        update->is_ignore = $3;
        update->table_refs = $4;
        set_vector(update->set_list, $6);
        update->where = $7;
        $$ = update;
    }
    ;
TruncateStmt:
    TRUNCATE TABLE TableName PartitionNameListOpt {
        TruncateStmt* truncate_stmt = new_node(TruncateStmt);
        truncate_stmt->location = parser->get_location(@1, @$);
        truncate_stmt->table_name = $3;
        set_vector(truncate_stmt->partition_names, $4);
        $$ = truncate_stmt;
    }
    | TRUNCATE TableName PartitionNameListOpt {
        TruncateStmt* truncate_stmt = new_node(TruncateStmt);
        truncate_stmt->location = parser->get_location(@1, @$);
        truncate_stmt->table_name = $2;
        set_vector(truncate_stmt->partition_names, $3);
        $$ = truncate_stmt;
    }
    ;

DeleteStmt:
    DELETE PriorityOpt QuickOptional IgnoreOptional FROM TableName PartitionNameListOpt WhereClauseOptional OrderByOptional LimitClause {
        DeleteStmt* delete_stmt = new_node(DeleteStmt);
        delete_stmt->location = parser->get_location(@1, @$);
        delete_stmt->priority = (PriorityEnum)$2;
        delete_stmt->is_quick = $3;
        delete_stmt->is_ignore = (bool)$4;
        delete_stmt->table_name = $6;
        set_vector(delete_stmt->partition_names, $7);
        delete_stmt->where = $8;
        delete_stmt->order = $9;
        delete_stmt->limit = $10;
        $$ = delete_stmt;
    }
    | DELETE PriorityOpt QuickOptional IgnoreOptional TableNameList FROM TableRefs WhereClauseOptional {
        DeleteStmt* delete_stmt = new_node(DeleteStmt);
        delete_stmt->location = parser->get_location(@1, @$);
        delete_stmt->priority = (PriorityEnum)$2;
        delete_stmt->is_quick = (bool)$3;
        delete_stmt->is_ignore = (bool)$4;
        set_vector(delete_stmt->delete_table_list, $5);
        delete_stmt->from_table = $7;
        delete_stmt->where = $8;
        $$ = delete_stmt;
    }
    ;

TableRefs:
    EscapedTableRef {
        $$ = $1;    
    }
    | TableRefs ',' EscapedTableRef {
        JoinNode* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $3;
        auto ref = new_node(TableRef);
        ref->join_node= join_node;
        $$ = ref;
    }
    ;
EscapedTableRef:
    TableRef %prec lowerThanSetKeyword {
        $$ = $1; 
    }
    | '{' AllIdent TableRef '}' {
        $3->location = parser->get_location(@1, @$);
        $$ = $3;
    }
    ;
TableRef:
    TableFactor {
        $$ = $1; 
    }
    | JoinTable {
        $$ = $1;
    }
    ;

TableFactor:
    TableName PartitionNameListOpt TableAsNameOpt IndexHintListOpt {
        TableSource* table_source = new_node(TableSource);
        table_source->location = parser->get_location(@1, @$);
        table_source->table_name = $1;
        set_vector(table_source->partition_names, $2);
        table_source->as_name = $3;
        set_vector(table_source->index_hints, $4);
        auto ref = new_node(TableRef);
        ref->source = table_source;
        $$ = ref;
    }
    | '(' SelectStmt ')' TableAsName {
        TableSource* table_source = new_node(TableSource);
        table_source->location = parser->get_location(@1, @$);
        SelectStmt* select = (SelectStmt*)$2;
        select->is_in_braces = true;
        table_source->derived_table = select;
        table_source->as_name = $4;
        auto ref = new_node(TableRef);
        ref->source = table_source;
        $$ = ref;
    }
    | '(' UnionStmt ')' TableAsName {
        TableSource* table_source = new_node(TableSource);
        table_source->location = parser->get_location(@1, @$);
        UnionStmt* union_stmt = (UnionStmt*)$2;
        union_stmt->is_in_braces = true;
        table_source->derived_table = union_stmt;
        table_source->as_name = $4;
        auto ref = new_node(TableRef);
        ref->source = table_source;
        $$ = ref;
    }
    | '(' WithClause SelectStmt ')' TableAsName {
        TableSource* table_source = new_node(TableSource);
        table_source->location = parser->get_location(@1, @$);
        SelectStmt* select = (SelectStmt*)$3;
        select->with = (WithClause*)$2;
        select->is_in_braces = true;
        table_source->derived_table = select;
        table_source->as_name = $5;
        auto ref = new_node(TableRef);
        ref->source = table_source;
        $$ = ref;
    }
    | '(' WithClause UnionStmt ')' TableAsName {
        TableSource* table_source = new_node(TableSource);
        table_source->location = parser->get_location(@1, @$);
        UnionStmt* union_stmt = (UnionStmt*)$3;
        for (int i = 0; i < union_stmt->select_stmts.size(); ++i) {
            union_stmt->select_stmts[i]->with = (cantor::parser::WithClause*)$2;
        }
        union_stmt->is_in_braces = true;
        table_source->derived_table = union_stmt;
        table_source->as_name = $5;
        auto ref = new_node(TableRef);
        ref->source = table_source;
        $$ = ref;
    }
    | '(' TableRefs ')' {
        $$ = $2; 
    }
    ;

PartitionNameListOpt:
    {
        $$ = nullptr;
    }
    | PARTITION '(' PartitionNameList ')' {
        $$ = $3;
    }
    ;
PartitionNameList:
    AllIdent {
        Vector<String>* string_list = new_node(Vector<String>);
        string_list->reserve(2, parser->arena);
        string_list->push_back($1, parser->arena);
        $$ = string_list;
    }
    | PartitionNameList ',' AllIdent {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

TableAsNameOpt:
    {
        $$ = nullptr;
    }
    | TableAsName {
        $$ = $1; 
    }
    ;

TableAsName:
    AllIdent {
        $$ = $1;
    }
    | AS AllIdent {
        $$ = $2;
    }
    ;

IndexHintListOpt: 
    {
        $$ = nullptr;                
    }
    | IndexHintList {
        $$ = $1; 
    }
    ;

ForceOrNot:
    {
        $$ = false;
    } 
    | FORCE {
        $$ = true;
    }
    ;

IndexHintList:
    IndexHint {
        $$ = append_vector<IndexHint*>(nullptr, $1, parser->arena, 10);
    }
    | IndexHintList IndexHint {
        $1->push_back($2, parser->arena);
        $$ = $1;
    }
    ;

IndexHint:
    IndexHintType IndexHintScope '(' IndexNameList ')' {
        IndexHint* index_hint = new_node(IndexHint);
        index_hint->location = parser->get_location(@1, @$);
        index_hint->hint_type = (cantor::pb::ast::IndexHintType)$1;
        index_hint->hint_scope = (cantor::pb::ast::IndexHintScope)$2;
        if ($4 != nullptr) {
            for (int i = 0; i < $4->size(); ++i) {
                index_hint->index_name_list.push_back((*$4)[i], parser->arena); 
            }
        }
        $$ = index_hint;
    }
    ;

IndexHintType:
    USE KeyOrIndex {
        $$ = cantor::pb::ast::IHT_HINT_USE;
    }
    | IGNORE KeyOrIndex {
        $$ = cantor::pb::ast::IHT_HINT_IGNORE;
    }
    | FORCE KeyOrIndex {
        $$ = cantor::pb::ast::IHT_HINT_FORCE;
    }
    ;

KeyOrIndex: 
    KEY | INDEX
    ;

KeyOrIndexOpt:
    {
    }
    | KeyOrIndex
    ;

IndexHintScope:
    {
        $$ = cantor::pb::ast::IHS_HINT_SCAN;
    }
    | FOR JOIN {
        $$ = cantor::pb::ast::IHS_HINT_JOIN;
    }
    | FOR ORDER BY {
        $$ = cantor::pb::ast::IHS_HINT_ORDER_BY;
    }
    | FOR GROUP BY {
        $$ = cantor::pb::ast::IHS_HINT_GROUP_BY;
    }
    ;

IndexNameList: {
        $$ = nullptr; 
    }
    | AllIdentOrPrimary {
        Vector<String>* string_list = new_node(Vector<String>);
        string_list->reserve(10, parser->arena);
        string_list->push_back($1, parser->arena);
        $$ = string_list;
    }
    | IndexNameList ',' AllIdentOrPrimary {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

AllIdentOrPrimary: 
    AllIdent {
        $$ = $1;
    }
    | PRIMARY {
        $$ = $1;
    }
    ;
JoinTable:
    /* Use %prec to evaluate production TableRef before cross join */
    TableRef CrossOpt TableRef %prec tableRefPriority {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $3;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef CrossOpt TableRef ON Expr {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $3;
        join_node->expr = $5;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef CrossOpt TableRef USING '(' RangeVarColumnNameList ')' {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left =  $1;
        join_node->right = $3;
        set_vector(join_node->using_col, $6);
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef JoinType OuterOpt JOIN TableRef ON Expr {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left =  $1;
        join_node->join_type = (cantor::pb::ast::JoinType)$2;
        join_node->right = $5;
        join_node->expr = $7;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef JoinType OuterOpt JOIN TableRef USING '(' RangeVarColumnNameList ')' {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->join_type = (cantor::pb::ast::JoinType)$2;
        join_node->right = $5;
        set_vector(join_node->using_col, $8);
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef STRAIGHT_JOIN TableRef {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $3;
        join_node->is_straight = true;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef STRAIGHT_JOIN TableRef ON Expr {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $3;
        join_node->is_straight = true;
        join_node->expr = $5;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef NATURAL JOIN TableRef {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $4;
        join_node->is_natural = true;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef NATURAL INNER JOIN TableRef {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->right = $5;
        join_node->is_natural = true;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    | TableRef NATURAL JoinType OuterOpt JOIN TableRef {
        auto* join_node = new_node(JoinNode);
        join_node->location = parser->get_location(@1, @$);
        join_node->left = $1;
        join_node->join_type = (cantor::pb::ast::JoinType)$3;
        join_node->right = $6;
        join_node->is_natural = true;
        auto ref = new_node(TableRef);
        ref->join_node = join_node;
        $$ = ref;
    }
    ;

JoinType:
    LEFT {
        $$ = cantor::pb::ast::JT_LEFT_JOIN;
    }
    | RIGHT {
        $$ = cantor::pb::ast::JT_RIGHT_JOIN;
    }
    | FULL {
        $$ = cantor::pb::ast::JT_FULL_JOIN;
    }
    ;
OuterOpt:
    {}
    | OUTER
    ;

CrossOpt:
    JOIN | CROSS JOIN | INNER JOIN
    ;

LimitClause:
    {
        $$ = nullptr;
    }
    | LIMIT SimpleExpr {
        auto count = $2;
        auto limit = new_node(LimitClause);
        count->location = parser->get_location(@2);
        limit->location = parser->get_location(@1, @$);
        limit->count = count;
        limit->offset = ConstantExpr::make_int("0", parser->arena);
        $$ = limit;
    }
    | LIMIT SimpleExpr ',' SimpleExpr {
        auto offset = $2;
        auto count = $4;
        LimitClause* limit = new_node(LimitClause);
        offset->location = parser->get_location(@2);
        count->location = parser->get_location(@4);
        limit->location = parser->get_location(@1, @$);
        limit->offset = offset;
        limit->count = count;
        $$ = limit;
    }
    | LIMIT SimpleExpr OFFSET SimpleExpr {
        auto count = $2;
        auto offset = $4;
        LimitClause* limit = new_node(LimitClause);
        offset->location = parser->get_location(@4);
        count->location = parser->get_location(@2);
        limit->location = parser->get_location(@1, @$);
        limit->offset = offset;
        limit->count = count;
        $$ = limit;
    }
    ;

WhereClause:
    WHERE Expr {
        auto node = new_node(WhereClause);
        node->expr = $2;
        node->location = parser->get_location(@1, @$);
        $$ = node;
    }
    ;
WhereClauseOptional: 
    {
        $$ = nullptr;
    }
    | WhereClause {
        $$ = $1;
    }
    ;
HavingClauseOptional: 
    {
        $$ = nullptr;
    }
    | HAVING Expr {
        auto node = new_node(HavingClause);
        node->expr = $2;
        node->location = parser->get_location(@1, @$);
        $$ = node;
    }
    ;
OrderByOptional: 
    {
        $$ = nullptr; 
    }
    | OrderBy {
        $$ = $1;
    }
    ;
OrderBy:
    ORDER BY ByList {
        OrderByClause* order = new_node(OrderByClause);
        order->location = parser->get_location(@1, @$);
        set_vector(order->items, $3);
        $$ = order;
    }
    ;

GroupByOptional:
    {
        $$ = nullptr;
    }
    | GroupBy {
        $$ = $1;
    }
    ;
GroupBy:
    GROUP BY ByList 
    {
        GroupByClause* group = new_node(GroupByClause);
        group->location = parser->get_location(@1, @$);
        set_vector(group->items, $3);
        $$ = group;
    }
    ;
ByList:
    ByItem {
        $$ = append_vector<ByItem*>(nullptr, $1, parser->arena);
    }
    | ByList ',' ByItem {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;
ByItem:
    Expr Order 
    {
        ByItem* item = new_node(ByItem);
        item->location = parser->get_location(@1, @$);
        item->expr = $1;
        item->is_desc = $2;
        $$ = item;
    }

Order:
    /* EMPTY */ {
        $$ = false; // ASC by default
    }
    | ASC {
        $$ = false;
    }
    | DESC {
        $$ = true;
    }
    ;
SelectStmtOpts:
    DefaultFalseDistinctOpt PriorityOpt SelectStmtStraightJoin SelectStmtSQLCache SelectStmtCalcFoundRows {
        $$ = new_node(SelectStmtOpts);
        $$->location = parser->get_location(@1, @$);
        $$->distinct = $1;
        $$->priority = (PriorityEnum)$2;
        $$->straight_join = $3;
        $$->sql_cache = $4;
        $$->calc_found_rows = $5;
    }
    ;
SelectStmtBasic:
    SELECT OptTagOption SelectStmtOpts SelectFieldList {
        SelectStmt* select = new_node(SelectStmt);
        select->location = parser->get_location(@1, @$);
        select->option = $2;
        select->select_opt = $3;
        set_vector(select->fields, $4);
        $$ = select;
    }
    ;
SelectStmtFromDual:
    SelectStmtBasic FromDual WhereClauseOptional {
        SelectStmt* select = (SelectStmt*)$1;
        select->location = parser->get_location(@1, @$);
        select->where = $3;
        $$ = select;
    }
    ;
SelectStmtFromTable:
    SelectStmtBasic FROM TableRefs WhereClauseOptional GroupByOptional HavingClauseOptional {
        SelectStmt* select = (SelectStmt*)$1;
        select->location = parser->get_location(@1, @$);
        select->table_refs = $3;
        select->where = $4;
        select->group = $5;
        select->having = $6;
        $$ = select;
    } 
    ;

SelectStmt:
    SelectStmtBasic OrderByOptional LimitClause SelectLockOpt {
        SelectStmt* select = (SelectStmt*)$1;
        select->location = parser->get_location(@1, @$);
        select->order = $2;
        select->limit = $3;
        select->lock = $4;
        $$ = select;
    }
    | SelectStmtFromDual LimitClause SelectLockOpt {
        SelectStmt* select = (SelectStmt*)$1;
        select->location = parser->get_location(@1, @$);
        select->limit = $2;
        select->lock = $3;
        $$ = select;
    }
    | SelectStmtFromTable OrderByOptional LimitClause SelectLockOpt {
        SelectStmt* select = (SelectStmt*)$1;
        select->location = parser->get_location(@1, @$);
        select->order = $2;
        select->limit = $3;
        select->lock = $4;
        $$ = select;
    }
    ;
SelectLockOpt:
    /* empty */
    {
        $$ = cantor::pb::ast::SL_NONE;
    }
    | FOR UPDATE {
        $$ = cantor::pb::ast::SL_FOR_UPDATE;
    }
    | LOCK IN SHARE MODE {
        $$ = cantor::pb::ast::SL_IN_SHARE;
    }
    ;
FromDual:
    FROM DUAL
    ;

SelectStmtCalcFoundRows: {
        $$ = false;
    }
    | SQL_CALC_FOUND_ROWS {
        $$ = true;
    }
    ;
SelectStmtSQLCache:
    %prec empty {
        $$ = false;
    }
    | SQL_CACHE {
        $$ = true;
    }
    | SQL_NO_CACHE {
        $$ = false;
    }
    ;
SelectStmtStraightJoin:
    {
        $$ = false;
    }
    | STRAIGHT_JOIN {
        $$ = true;
    }
    ;
SelectFieldList:
    SelectField {
        $$ = append_vector<SelectField*>(nullptr, $1, parser->arena, 10);
    }
    | SelectFieldList ',' SelectField {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

SelectField:
    '*'
    {
       auto* select_field = new_node(SelectField);
       select_field->location = parser->get_location(@1);
       select_field->wild_card = true;
       $$ = select_field;
    }
    | AllIdent '.' '*'
    {
       auto* select_field = new_node(SelectField);
       select_field->location = parser->get_location(@1, @$);
       select_field->wild_card = true;
       select_field->wild_uri.table_name = $1;
       $$ = select_field;
    }
    | AllIdent '.' AllIdent '.' '*'
    {
       auto* select_field = new_node(SelectField);
       select_field->location = parser->get_location(@1, @$);
       select_field->wild_card = true;
       select_field->wild_uri.db_name = $1;
       select_field->wild_uri.table_name = $3;
       $$ = select_field;
    }
    | AllIdent '.' AllIdent '.' AllIdent '.' '*'
    {
       auto* select_field = new_node(SelectField);
       select_field->location = parser->get_location(@1, @$);
       select_field->wild_card = true;
        select_field->wild_uri.ns_name = $1;
       select_field->wild_uri.db_name = $3;
       select_field->wild_uri.table_name = $5;
       $$ = select_field;
    }
    | Expr FieldAsNameOpt
    {
        auto* select_field = new_node(SelectField);
        select_field->location = parser->get_location(@1, @$);
        select_field->expr = $1;
        select_field->as_name = $2;
        if (select_field->expr->expr_type != cantor::pb::ast::ENT_RANGE_VAR
            && !select_field->expr->is_constant_expr()) {
            select_field->org_name.strdup(@1.start, @1.end - @1.start, parser->arena);
        }
        $$ = select_field;
    }
    | '{' AllIdent Expr '}' FieldAsNameOpt {
        auto* select_field = new_node(SelectField);
        select_field->location = parser->get_location(@1, @$);
        select_field->expr = $3;
        select_field->as_name = $5;
        $$ = select_field;
    }
    ;
FieldAsNameOpt:
    /* EMPTY */
    {
        $$ = nullptr;
    }
    | FieldAsName {
        $$ = $1;
    }

FieldAsName:
    AllIdent {
        $$ = $1;
    }
    | AS AllIdent {
        $$ = $2;
    }
    | STRING_LIT {
        $$ = ((ConstantExpr*)$1)->_u.str_val;
    }
    | AS STRING_LIT {
        $$ = ((ConstantExpr*)$2)->_u.str_val;
    }
    ;

SubSelect:
    '(' SelectStmt ')' {
        auto sub_query = new_node(SubqueryExpr);
        sub_query->location = parser->get_location(@1, @$);
        sub_query->query_stmt = (SelectStmt*)$2;
        $$ = sub_query;
    }
    | '(' UnionStmt ')' {
        auto sub_query = new_node(SubqueryExpr);
        sub_query->location = parser->get_location(@1, @$);
        sub_query->query_stmt = (UnionStmt*)$2;
        $$ = sub_query;
    }
    | '(' SubSelect ')' {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    | '(' WithClause SelectStmt ')' {
        auto sub_query = new_node(SubqueryExpr);
        sub_query->location = parser->get_location(@1, @$);
        SelectStmt* select = (SelectStmt*)$3;
        select->with = (WithClause*)$2;
        sub_query->query_stmt = select;
        $$ = sub_query;
    }
    | '(' WithClause UnionStmt ')' {
        auto sub_query = new_node(SubqueryExpr);
        sub_query->location = parser->get_location(@1, @$);
        UnionStmt* union_stmt = (UnionStmt*)$3;
        for (int i = 0; i < union_stmt->select_stmts.size(); ++i) {
            union_stmt->select_stmts[i]->with = (cantor::parser::WithClause*)$2;
        }
        sub_query->query_stmt = union_stmt;
        $$ = sub_query;
    }
    ;

// See https://dev.mysql.com/doc/refman/5.7/en/union.html
UnionStmt:
    UnionClauseList UNION UnionOpt SelectStmtBasic OrderByOptional LimitClause SelectLockOpt {
        UnionStmt* union_stmt = (UnionStmt*)$1;
        union_stmt->location = parser->get_location(@1, @$);
        if (!union_stmt->distinct) {
            union_stmt->distinct = $3;
        }
        union_stmt->select_stmts.push_back((SelectStmt*)$4, parser->arena);
        union_stmt->order = $5;
        union_stmt->limit = $6;        
        union_stmt->lock = $7;
        $$ = union_stmt;
    }
    | UnionClauseList UNION UnionOpt SelectStmtFromTable OrderByOptional LimitClause SelectLockOpt {
        UnionStmt* union_stmt = (UnionStmt*)$1;
        union_stmt->location = parser->get_location(@1, @$);
        if (!union_stmt->distinct) {
            union_stmt->distinct = $3;
        }
        union_stmt->select_stmts.push_back((SelectStmt*)$4, parser->arena);
        union_stmt->order = $5;
        union_stmt->limit = $6;        
        union_stmt->lock = $7;
        $$ = union_stmt;
    }
    | UnionClauseList UNION UnionOpt '(' SelectStmt ')' OrderByOptional LimitClause SelectLockOpt {
        UnionStmt* union_stmt = (UnionStmt*)$1;
        union_stmt->location = parser->get_location(@1, @$);
        if (!union_stmt->distinct) {
            union_stmt->distinct = $3;
        }
        SelectStmt* select = (SelectStmt*)$5;
        select->is_in_braces = true;
        union_stmt->select_stmts.push_back(select, parser->arena);
        union_stmt->order = $7;
        union_stmt->limit = $8;
        union_stmt->lock = $9;
        $$ = union_stmt;
    }
    ;

UnionClauseList:
    UnionSelect {
        UnionStmt* union_stmt = new_node(UnionStmt);
        union_stmt->location = parser->get_location(@1, @$);
        union_stmt->select_stmts.push_back((SelectStmt*)$1, parser->arena);
        $$ = union_stmt;
    }
    | UnionClauseList UNION UnionOpt UnionSelect {
        UnionStmt* union_stmt = (UnionStmt*)$1;
        union_stmt->location = parser->get_location(@1, @$);
        if (!union_stmt->distinct) {
            union_stmt->distinct = $3;
        }
        union_stmt->select_stmts.push_back((SelectStmt*)$4, parser->arena);
        $$ = union_stmt;
    }
    ;

UnionSelect:
    SelectStmt {
        $$ = $1;
    }
    | '(' SelectStmt ')' {
        SelectStmt* select = (SelectStmt*)$2;
        select->location = parser->get_location(@1, @$);
        select->is_in_braces = true;
        $$ = select;
    }
    ;

UnionOpt:
    DefaultTrueDistinctOpt
    ;

/*Expr*/
Expr:
    Operators { $$ = $1;}
    | PredicateOp { $$ = $1;}
    | SimpleExpr
    {
        $1->location = parser->get_location(@1, @$);
        $$ = $1;
    }
    ;

FunctionCall:
        BuildInFun {
            $$ = $1;
        }
        | CtorFuncExpr
        {
            $$ = $1;
        }
        | IDENT '(' ')'
        {
            FuncExpr* fun = new_node(FuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->fn_name = $1;
            $$ = fun;
        }
        | IDENT '(' ExprList ')'
        {
            FuncExpr* fun = new_node(FuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->fn_name = $1;
            fun->exprs = $3->sub_exprs;
            $$ = fun;
        }
        ;
CtorFuncExpr:
        ENUM OptTypeNameUri '{' INTEGER_LIT '}'
        {
            auto* fun = new_node(CtorEnumFuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->arg.location = parser->get_location(@2, @$);
            fun->arg.uri = $2;
            fun->arg.field = nullptr;
            fun->arg.is_field = false;
            fun->arg.value = ((ConstantExpr*)$4)->_u.int64_val;
            $$ = fun;
        }
        | ENUM OptTypeNameUri '{' STRING_LIT '}'
        {
            auto* fun = new_node(CtorEnumFuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->arg.location = parser->get_location(@2, @$);
            fun->arg.uri = $2;
            fun->arg.is_field = true;
            fun->arg.field = ((ConstantExpr*)$4)->_u.str_val;
            $$ = fun;
        }
        | PROTO STRING_LIT '{' OptDictPairListOptComma '}'
        {
              auto* fun = new_node(CtorProtoFuncExpr);
              fun->location = parser->get_location(@1, @$);
              fun->arg.uri = ((ConstantExpr*)$2)->_u.str_val;
              set_vector(fun->arg.values, $4);
              $$ = fun;
        }
        | JSON '{' OptDictPairListOptComma '}'
        {
                auto* fun = new_node(CtorJsonFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $3);
                $$ = fun;

        }
        | STRUCT '{' OptDictPairListOptComma '}'
        {
              auto* fun = new_node(CtorStructFuncExpr);
              fun->location = parser->get_location(@1, @$);
              fun->arg.location = parser->get_location(@2, @$);
              set_vector(fun->arg.values, $3);
              $$ = fun;

        }
        | MAP '{' OptMapArgListOptComma '}'
        {
            auto* fun = new_node(CtorMapFuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->arg.location = parser->get_location(@2, @$);
            set_vector(fun->arg.values, $3);
            $$ = fun;

        }
        | UNION '{' OptDictPair '}'
        {
                auto* fun = new_node(CtorUnionFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg = $3;
                $$ = fun;

        }
        | '[' OptArrayExprListOptComma ']'
        {
                auto* fun = new_node(CtorArrayFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $2);
                $$ = fun;

        }
        | ARRAY '[' OptArrayExprListOptComma ']'
        {
                auto* fun = new_node(CtorArrayFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $3);
                $$ = fun;

        }
        | TUPLE '{' OptArrayExprListOptComma '}'
        {
                auto* fun = new_node(CtorTupleFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $3);
                $$ = fun;

        }
        | SET '{' OptArrayExprListOptComma '}'
        {
                auto* fun = new_node(CtorSetFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $3);
                $$ = fun;

        }
        | JSON '[' OptArrayExprListOptComma ']'
        {
                auto* fun = new_node(CtorJsonArrayFuncExpr);
                fun->location = parser->get_location(@1, @$);
                fun->arg.location = parser->get_location(@2, @$);
                set_vector(fun->arg.values, $3);
                $$ = fun;

        }
        | STRUCT TypeNameUri '{' OptDictPairListOptComma '}'
        {
              auto* fun = new_node(CtorUdfFuncExpr);
              fun->location = parser->get_location(@1, @$);
              fun->arg.location = parser->get_location(@2, @$);
              fun->arg.uri = $2;
              auto sub_arg = new_node(DictArg);
              sub_arg->location = parser->get_location(@4);
              set_vector(sub_arg->values, $4);
              fun->arg.set_arg(sub_arg);
              $$ = fun;

        }
        | STRUCT TypeNameUri '{' OptArrayExprListOptComma '}'
        {
            auto* fun = new_node(CtorUdfFuncExpr);
            fun->location = parser->get_location(@1, @$);
            fun->arg.location = parser->get_location(@2, @$);
            fun->arg.uri = $2;
            auto sub_arg = new_node(ExprListArg);
            sub_arg->location = parser->get_location(@4);
            set_vector(sub_arg->values, $4);
            fun->arg.set_arg(sub_arg);
            $$ = fun;

        }
        | STRUCT TypeNameUri '{' OptMapArgListOptComma '}'
        {
              auto* fun = new_node(CtorUdfFuncExpr);
              fun->location = parser->get_location(@1, @$);
              fun->arg.location = parser->get_location(@2, @$);
              fun->arg.uri = $2;
              auto sub_arg = new_node(MapArg);
              sub_arg->location = parser->get_location(@4);
              set_vector(sub_arg->values, $4);
              fun->arg.set_arg(sub_arg);
              $$ = fun;

        }
        ;

BuildInFun:
    SumExpr
    | FunctionCallNonKeyword 
    | FunctionCallKeyword
    ;
FuncDatetimePrecListOpt:
    {
        $$ = nullptr;
    }
    | INTEGER_LIT {
        $$ = $1;        
    } 
    ;
FunctionNameDateArithMultiForms:
    ADDDATE | SUBDATE
    ;
FunctionNameDateArith:
     DATE_ADD | DATE_SUB
     ;
FunctionNameSubstring:
    SUBSTR | SUBSTRING | MID
    ;

TimestampUnit:
    MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | SECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | MINUTE {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | HOUR {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | WEEK {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    } 
    | MONTH {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    } 
    | QUARTER {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | YEAR {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    ;

TimeUnit:
    MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | SECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    } 
    | MINUTE {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    } 
    | HOUR {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    } 
    | WEEK {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | MONTH {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | QUARTER {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | YEAR {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | SECOND_MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | MINUTE_MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | MINUTE_SECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | HOUR_MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | HOUR_SECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | HOUR_MINUTE {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY_MICROSECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY_SECOND {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY_MINUTE {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | DAY_HOUR {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    | YEAR_MONTH {
        $$ = ConstantExpr::make_string($1.to_lower_inplace(), parser->arena);
        $$->location = parser->get_location(@1);
    }
    ;

TrimDirection:
    BOTH {
        $$ = ConstantExpr::make_string($1, parser->arena);
        $$->location = parser->get_location(@1);
    }
    | LEADING {
        $$ = ConstantExpr::make_string($1, parser->arena);
        $$->location = parser->get_location(@1);
    }
    | TRAILING {
        $$ = ConstantExpr::make_string($1, parser->arena);
        $$->location = parser->get_location(@1);
    }
    ;

FunctionNameCurtime:
    CURTIME | CURRENT_TIME
    ;

FunctionaNameCurdate:
    CURDATE | CURRENT_DATE
    ;

FunctionaNameDateRelate:
    HOUR | DAY | MONTH | YEAR | DATE
    ;

FunctionCallNonKeyword: 
    FunctionNameCurtime '(' FuncDatetimePrecListOpt ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        if ($3 != nullptr) {
            fun->exprs.push_back($3, parser->arena);
        }
        $$ = fun;
    }
    | CURRENT_TIME {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | FunctionaNameCurdate '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | CURRENT_DATE {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | FunctionaNameDateRelate '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | WEEK '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;      
    }
    | WEEK '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;         
    }
    | MINUTE '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | SECOND '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | TIME '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | QUARTER '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | MICROSECOND '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | SYSDATE '(' FuncDatetimePrecListOpt ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        if ($3 != nullptr) {
            fun->exprs.push_back($3, parser->arena);
        }
        $$ = fun;
    }
    | NOW '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | NOW '(' INTEGER_LIT ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | UTC_TIMESTAMP '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun;
    }
    | UTC_TIMESTAMP '(' INTEGER_LIT ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | TIMESTAMP '(' ExprList ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs = $3->sub_exprs;
        $$ = fun;
    }
    | FunctionNameDateArithMultiForms '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        ConstantExpr* day_expr = ConstantExpr::make_string("DAY", parser->arena);
        fun->exprs.push_back(day_expr, parser->arena);
        $$ = fun;
    }
    | FunctionNameDateArithMultiForms '(' Expr ',' INTERVAL Expr TimeUnit ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($6, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun;
    }
    | FunctionNameDateArith '(' Expr ',' INTERVAL Expr TimeUnit ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($6, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' DATE ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_date";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS DATE ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_date";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' DATETIME ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_datetime";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS DATETIME ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_datetime";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' TIME ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_time";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS TIME ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_time";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    /*
    | CONVERT '(' Expr ',' StringType ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_string";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS StringType ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_string";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    */
    | CONVERT '(' Expr ',' SIGNED INTEGER')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_signed";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' SIGNED ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_signed";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS SIGNED INTEGER')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_signed";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS SIGNED ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_signed";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' UNSIGNED INTEGER')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_unsigned";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' UNSIGNED')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_unsigned";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS UNSIGNED INTEGER')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_unsigned";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS UNSIGNED ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_unsigned";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' BINARY ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_string";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS BINARY ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_string";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CONVERT '(' Expr ',' DECIMAL ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_double";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | CAST '(' Expr AS DECIMAL ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "cast_to_double";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | USER '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | SYSTEM_USER '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | SESSION_USER '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun; 
    }
    | EXTRACT '(' TimeUnit FROM Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | POSITION '(' Expr IN Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | FunctionNameSubstring '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "substr";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | FunctionNameSubstring '(' Expr FROM Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "substr";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | FunctionNameSubstring '(' Expr ',' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "substr";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun;
    }
    | FunctionNameSubstring '(' Expr FROM Expr FOR Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "substr";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun;
    }
    | TIMESTAMPADD '(' TimestampUnit ',' Expr ',' Expr ')' {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_COMMON, $3, $5, $7, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        $$ = fun;
    }
    | TIMESTAMPDIFF '(' TimestampUnit ',' Expr ',' Expr ')' {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_COMMON, $3, $5, $7, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "timestampdiff";
        $$ = fun;
    }
    | TRIM '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | TRIM '(' Expr FROM Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | TRIM '(' TrimDirection FROM Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back(nullptr, parser->arena);
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | TRIM '(' TrimDirection Expr FROM Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = $1;
        fun->exprs.push_back($6, parser->arena);
        fun->exprs.push_back($4, parser->arena);
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | MOD '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "mod";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | FORMAT '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "format";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | INSERT '(' Expr ',' Expr ',' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "insert";
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        fun->exprs.push_back($9, parser->arena);
        $$ = fun;
    }
    | ASCII '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "ascii";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | STRING '(' ExprList ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "string";
        fun->exprs = $3->sub_exprs;
        $$ = fun;
    }
    ;
FunctionCallKeyword:
    VALUES '(' RangeVar ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "values";
        fun->func_type = cantor::pb::ast::FT_VALUES;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun; 
    }
    | LEFT '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "left";
        fun->func_type = cantor::pb::ast::FT_COMMON;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun; 
    }
    | RIGHT '(' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "right";
        fun->func_type = cantor::pb::ast::FT_COMMON;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun; 
    }
    | REPLACE '(' Expr ',' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "replace";
        fun->func_type = cantor::pb::ast::FT_COMMON;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun; 
    }
    | IF '(' Expr ',' Expr ',' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "if";
        fun->func_type = cantor::pb::ast::FT_COMMON;
        fun->exprs.push_back($3, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        fun->exprs.push_back($7, parser->arena);
        $$ = fun; 
    }
    | DATABASE '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "database";
        $$ = fun;
    }
    | DEFAULT '(' RangeVar ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "default";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | FunctionNameCurTimestamp {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "current_timestamp";
        $$ = fun;
    }
    | FunctionNameCurTimestamp '(' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "current_timestamp";
        $$ = fun;
    }
    | FunctionNameCurTimestamp '(' INTEGER_LIT ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "current_timestamp";
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    ;
SumExpr:
    AVG '(' BuggyDefaultFalseDistinctOpt Expr')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = $3;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;    
    }
    | BIT_AND '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun; 
    }
    | BIT_AND '(' ALL Expr ')' { 
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | BIT_OR '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | BIT_OR '(' ALL Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun; 
    }
    | BIT_XOR '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | BIT_XOR '(' ALL Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | COUNT '(' DistinctKwd ExprList ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = true;
        fun->exprs = $4->sub_exprs;
        $$ = fun;
    }
    | COUNT '(' ALL Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | COUNT '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    | COUNT '(' '*' ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = false; 
        fun->is_star = true;
        $$ = fun;
    }
    | MAX '(' BuggyDefaultFalseDistinctOpt Expr')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = $3;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | MIN '(' BuggyDefaultFalseDistinctOpt Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = $3;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | SUM '(' BuggyDefaultFalseDistinctOpt Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = $3;
        fun->exprs.push_back($4, parser->arena);
        $$ = fun;
    }
    | GROUP_CONCAT '(' BuggyDefaultFalseDistinctOpt ExprList OrderByOptional SeparatorOpt ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->distinct = $3;
        fun->exprs.push_back((RowExpr*)$4, parser->arena);
        fun->exprs.push_back($6, parser->arena);
        if ($5 != nullptr && $5->items.size() > 0) {
            auto by_expr_row = new_node(RowExpr);
            auto is_desc_row = new_node(RowExpr);
            for (int i = 0; i < $5->items.size(); i++) {
                by_expr_row->sub_exprs.push_back($5->items[i]->expr, parser->arena);
                ConstantExpr* is_desc = nullptr;
                if ($5->items[i]->is_desc) {
                    is_desc = ConstantExpr::make_true(parser->arena);
                } else {
                    is_desc = ConstantExpr::make_false(parser->arena);
                }
                is_desc_row->sub_exprs.push_back(is_desc, parser->arena);
            }
            fun->exprs.push_back(by_expr_row, parser->arena);
            fun->exprs.push_back(is_desc_row, parser->arena);
        }
        $$ = fun;
    }
    | USER_AGG '(' Expr ')' {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->func_type = cantor::pb::ast::FT_AGG;
        fun->fn_name = $1;
        fun->exprs.push_back($3, parser->arena);
        $$ = fun;
    }
    ;

SeparatorOpt:
    {
        $$ = ConstantExpr::make_string(",", parser->arena);
    }
    | SEPARATOR STRING_LIT {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    ;

DistinctKwd:
    DISTINCT
    | DISTINCTROW
    ;

DistinctOpt:
    ALL {
        $$ = false;
    }
    | DistinctKwd {
        $$ = true;
    }
    ;

DefaultFalseDistinctOpt:
    {
        $$ = false;
    }
    | DistinctOpt
    ;

DefaultTrueDistinctOpt:
    {
        $$ = true;
    }
    | DistinctOpt
    ;

BuggyDefaultFalseDistinctOpt:
    DefaultFalseDistinctOpt
    | DistinctKwd ALL {
        $$ = true;
    }
    ;

AllIdent:
    IDENT {}
    // UnReservedKeyword 
    | ACTION
    | AFTER
    | ALWAYS
    | ALGORITHM
    | ANY
    | ASCII
    | AUTO_INCREMENT
    | AUTOEXTEND_SIZE
    | AVG_ROW_LENGTH
    | AVG
    | BEGINX
    | TXN
    | WORK
    | BINLOG
    | BIT
    | BOOL
    | BOOLEAN
    | BTREE
    | BYTE
    | CASCADED
    | CHARSET
    | CHECKSUM
    | CLEANUP
    | CLIENT
    | COALESCE
    | COLLATION
    | COLUMNS
    | COMMENT
    | COMMIT
    | COMMITTED
    | COMPACT
    | COMPRESSED
    | COMPRESSION
    | CONNECTION
    | CONSISTENT
    | COPY
    | DAY
    | DATA
    | DATE
    | DATETIME
    | DEALLOCATE
    | DEFINER
    | DELAY_KEY_WRITE
    | DISABLE
    | DO
    | DUPLICATE
    | DYNAMIC
    | ENABLE
    | ENCRYPTION
    | END
    | ENGINE
    | ENGINES
    | ENGINE_ATTRIBUTE
    | ENUM
    | EVENT
    | EVENTS
    | ESCAPE
    | EXCLUSIVE
    | EXECUTE
    | FIELDS
    | FIRST
    | LAST
    | FIXED
    | FILE_T
    | FLUSH
    | FORMAT
    | FULL
    | FUNCTION
    | GRANTS
    | HASH
    | HOUR
    | IDENTIFIED
    | ISOLATION
    | INDEXES
    | INPLACE
    | INSERT_METHOD
    | INVOKER
    | INSTANT
    | JSON
    | KEY_BLOCK_SIZE
    | DYNAMIC_PARTITION_ATTR
    | LANGUAGE
    | LOCAL
    | LESS
    | LEVEL
    | MASTER
    | NANOSECOND
    | MICROSECOND
    | MILLISECOND
    | MINUTE
    | MODE
    | MODIFY
    | MONTH
    | MAX_ROWS
    | MAX_CONNECTIONS_PER_HOUR
    | MAX_QUERIES_PER_HOUR
    | MAX_UPDATES_PER_HOUR
    | MAX_USER_CONNECTIONS
    | MERGE
    | MIN_ROWS
    | NAMES
    | NATIONAL
    | NO
    | NONE
    | OFFSET
    | ONLY
    | PASSWORD
    | PARTITIONS
    | PLUGINS
    | PREPARE
    | PRIVILEGES
    | PROCESS
    | PROCESSLIST
    | PROFILES
    | PROXY
    | QUARTER
    | QUERY
    | QUERIES
    | QUICK
    | RECOVER
    | RESTORE
    | REDUNDANT
    | RELOAD
    | REPEATABLE
    | REPLICATION
    | ROLLBACK
    | ROUTINE
    | ROW
    | ROW_COUNT
    | ROW_FORMAT
    | SECOND
    | SECONDARY_ENGINE_ATTRIBUTE
    | SECURITY
    | SEPARATOR
    | SERIALIZABLE
    | SESSION
    | SHARE
    | SHARED
    | SIGNED
    | SLAVE
    | SNAPSHOT
    | SQL_CACHE
    | SQL_NO_CACHE
    | SHUTDOWN
    | START
    | STATS_PERSISTENT
    | STATS_AUTO_RECALC
    | STATS_SAMPLE_PAGES
    | STATUS
    | SUPER
    | SOME
    | GLOBAL
    | TABLES
    | TABLESPACE
    | TEMPORARY
    | TEMPTABLE
    | TEXT
    | THAN
    | TIME
    | TIMESTAMP
    | TRACE
    | TRANSACTION
    | TRIGGERS
    | TRUNCATE
    | UNCOMMITTED
    | UNKNOWN
    | USER
    | UNDEFINED
    | VALUE
    | VARIABLES
    | VIEW
    | WARNINGS
    | WEEK
    | YEAR
    /* builtin functions. */
    | ADDDATE
    | BIT_AND
    | BIT_OR
    | BIT_XOR
    | CAST
    | COUNT
    | CURDATE
    | CURTIME
    | DATE_ADD
    | DATE_SUB
    | EXTRACT
    | GROUP_CONCAT
    | MAX
    | MID
    | MIN
    | NOW
    | UTC_TIMESTAMP
    | POSITION
    | SESSION_USER
    | STD
    | STDDEV
    | STDDEV_POP
    | STDDEV_SAMP
    | SUBDATE
    | SUBSTR
    | SUBSTRING
    | TIMESTAMPADD
    | TIMESTAMPDIFF 
    | SUM
    | SYSDATE
    | SYSTEM_USER
    | TRIM
    | VARIANCE
    | VAR_POP
    | VAR_SAMP
    ;

NumLiteral:
    INTEGER_LIT {
    }
    | DECIMAL_LIT {
    }
    ;

Literal:
    NULLX {
        $$ = ConstantExpr::make_null(parser->arena);
        $$->location = parser->get_location(@1);
    }
    | TRUE {
        $$ = ConstantExpr::make_true(parser->arena);
        $$->location = parser->get_location(@1);
    }
    | FALSE {
        $$ = ConstantExpr::make_false(parser->arena);
        $$->location = parser->get_location(@1);
    }
    | NumLiteral {
    }
    | STRING_LIT {
    }
    | _BINARY STRING_LIT {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    | _UTF8MB4 STRING_LIT {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    ;

SimpleExpr:
    Literal {
    }
    | PLACE_HOLDER_LIT {
    }
    | RangeVar indirection_list_opt {
        if($2) {
            set_vector($1->indirections, $2);
        }
        $1->location = parser->get_location(@1, @$);
        $$ = $1;
    }
    | RowExpr %prec empty {
    }
    | FunctionCall indirection_list_opt
    {
        auto func = (FuncExpr*)$1;
        set_vector(func->indirections, $2);
        $1->location = parser->get_location(@1, @$);
        $$ = $1;
    }
    | SubSelect { 
    }
    | EXISTS SubSelect {
        ExistsSubqueryExpr*  exists_expr = new_node(ExistsSubqueryExpr);
        exists_expr->location = parser->get_location(@1, @$);
        exists_expr->query_expr = (SubqueryExpr*)$2;
        $$ = exists_expr;
    }
    | '-' SimpleExpr %prec NEG {
        $$ = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_UMINUS, $2, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | '+' SimpleExpr %prec NEG { 
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    | NOT SimpleExpr {
        if ($2->is_exists_subquery_expr()) {
            ExistsSubqueryExpr* exists_expr = (ExistsSubqueryExpr*)$2;
            exists_expr->is_not = true;
            exists_expr->location = parser->get_location(@1, @$);
            $$ = exists_expr;
        } else {
            $$ = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_LOGIC_NOT, $2, parser->arena);
            $$->location = parser->get_location(@1, @$);
        }
    }
    | NOT_OP SimpleExpr {
        if ($2->is_exists_subquery_expr()) {
            ExistsSubqueryExpr* exists_expr = (ExistsSubqueryExpr*)$2;
            exists_expr->is_not = true;
            $$ = exists_expr;
            $$->location = parser->get_location(@1, @$);
        } else {
            $$ = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_LOGIC_NOT, $2, parser->arena);
            $$->location = parser->get_location(@1, @$);
        }
    }
    | MATCH '(' RangeVarColumnNameList ')' AGAINST '(' SimpleExpr FulltextSearchModifierOpt ')' {
        auto row = new_node(RowExpr);
        set_vector(row->columns, $3);
        $$ = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_MATCH_AGAINST, row, $7, $8, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | '~' SimpleExpr {
        $$ = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_BIT_NOT, $2, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | '(' Expr ')' indirection_list_opt {
        if($4) {
            if($2->is_func_expr()) {
                auto func = (FuncExpr*)$2;
                set_vector(func->indirections, $4);
            } else if($2->is_rang_var()) {
                auto var = (RangeVar*)$2;
                set_vector(var->indirections, $4);
            }else {
                sql_error(&@2, yyscanner, parser, "only func expr or column can follow by indirection");
                return -1;
            }
        }
        $2->location = parser->get_location(@1, @$);
        $$ = $2;

    }
    | CASE Expr WhenClause END {
        auto* fun = new_node(FuncCaseWhenClause);
        $3->case_expr = $2;
        fun->when_clause = $3;
        fun->location = parser->get_location(@1, @$);
        $$ = fun;
    }
    | CASE WhenClause END {
        auto* fun = new_node(FuncWhenClause);
        fun->when_clause = $2;
        fun->location = parser->get_location(@1, @$);
        $$ = fun;
    }
    ;

FulltextSearchModifierOpt:
    {
        $$ = ConstantExpr::make_string("IN NATURAL LANGUAGE MODE", parser->arena);
    }
    | IN NATURAL LANGUAGE MODE {
        $$ = ConstantExpr::make_string("IN NATURAL LANGUAGE MODE", parser->arena);
         $$->location = parser->get_location(@1, @$);
    }
    | IN BOOLEAN MODE {
        $$ = ConstantExpr::make_string("IN BOOLEAN MODE", parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | IN VECTOR MODE {
        $$ = ConstantExpr::make_string("IN VECTOR MODE", parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    ;
WhenClause:
    WhenCaseList ElseOpt
    {
        auto node = new_node(WhenClause);
        node->location = parser->get_location(@1, @$);
        set_vector(node->when_clauses, $1);
        node->else_expr = $2;
        $$ = node;
    }
    ;

WhenCaseList:
    WhenCase {
        $$ = append_vector<WhenClauseCase*>(nullptr, $1, parser->arena);
    }
    | WhenCaseList WhenCase {
        $$ = append_vector<WhenClauseCase*>($1, $2, parser->arena);
    }
    ;
WhenCase:
    WHEN Expr THEN Expr {
        auto node = new_node(WhenClauseCase);
        node->location = parser->get_location(@1, @$);
        node->when_expr= $2;
        node->then_expr =$4;
        $$ = node;
    }
    ;
ElseOpt:
    {
        $$ = nullptr;
    }
    | ELSE Expr {
        $$ = $2;
    }
    ;

Operators:
    CompareSubqueryExpr {
        $$ = $1;
    }
    | Expr '+' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_ADD, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '-' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_MINUS, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '+' INTERVAL Expr TimeUnit {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "date_add";
        fun->exprs.push_back($1, parser->arena);
        fun->exprs.push_back($4, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | Expr '-' INTERVAL Expr TimeUnit {
        FuncExpr* fun = new_node(FuncExpr);
        fun->location = parser->get_location(@1, @$);
        fun->fn_name = "date_sub";
        fun->exprs.push_back($1, parser->arena);
        fun->exprs.push_back($4, parser->arena);
        fun->exprs.push_back($5, parser->arena);
        $$ = fun;
    }
    | Expr '*' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_MULTIPLIES, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '/' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_DIVIDES, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr MOD Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_MOD, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    } 
    | Expr MOD_OP Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_MOD, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    } 
    | Expr LS_OP Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_LS, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr RS_OP Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_RS, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '&' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_BIT_AND, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '|' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_BIT_OR, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr '^' Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_BIT_XOR, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr CompareOp Expr %prec EQ_OP {
        $$ = FuncExpr::new_binary_op_node((cantor::pb::ast::FuncType)$2, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr AND Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_LOGIC_AND, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr OR Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_LOGIC_OR, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | Expr XOR Expr {
        $$ = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_LOGIC_XOR, $1, $3, parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | BINARY Expr {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    | Expr COLLATE StringName {
        $$ = $1;
        $$->location = parser->get_location(@1, @$);
    }
    ;

CompareOp:
    GE_OP {
        $$ = cantor::pb::ast::FT_GE;
    }
    | GT_OP {
        $$ = cantor::pb::ast::FT_GT;
    }
    | LE_OP {
        $$ = cantor::pb::ast::FT_LE;
    }
    | LT_OP {
        $$ = cantor::pb::ast::FT_LT;
    }
    | NE_OP {
        $$ = cantor::pb::ast::FT_NE;
    }
    | EQ_OP {
        $$ = cantor::pb::ast::FT_EQ;
    }
    ;

CompareSubqueryExpr:
    Expr CompareOp AnyOrAll SubSelect %prec EQ_OP {
        CompareSubqueryExpr* comp_sub_query = new_node(CompareSubqueryExpr);
        comp_sub_query->location = parser->get_location(@1, @$);
        comp_sub_query->left_expr = $1;
        comp_sub_query->func_type = (cantor::pb::ast::FuncType)$2;
        comp_sub_query->cmp_type = (cantor::pb::ast::CompareType)$3;
        comp_sub_query->right_expr = (SubqueryExpr*)$4;
        $$ = comp_sub_query;
    }

PredicateOp:
    Expr IsOrNot NULLX %prec IS {
        FuncExpr* fun = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_IS_NULL, $1, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | Expr IsOrNot TRUE %prec IS {
        FuncExpr* fun = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_IS_TRUE, $1, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | Expr IsOrNot FALSE %prec IS {
        FuncExpr* fun = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_IS_TRUE, $1, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = !$2;
        $$ = fun;
    }
    | Expr IsOrNot UNKNOWN %prec IS {
        FuncExpr* fun = FuncExpr::new_unary_op_node(cantor::pb::ast::FT_IS_UNKNOWN, $1, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | SimpleExpr InOrNot '(' ExprList ')' %prec IN {
        FuncExpr* fun = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_IN, $1, $4, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | RowExpr InOrNot '(' RowExprList ')' %prec IN {
        FuncExpr* fun = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_IN, $1, $4, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | RowExpr InOrNot SubSelect %prec IN {
        FuncExpr* fun = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_IN, $1, $3, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | SimpleExpr InOrNot SubSelect %prec IN {
        FuncExpr* fun = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_IN, $1, $3, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    /*
    | SimpleExpr LikeOrNot SubSelect LikeEscapeOpt %prec LIKE {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_LIKE, $1, $3, $4, parser->arena);
        fun->is_not = $2;
        $$ = fun;
    }*/
    | SimpleExpr LikeOrNot SimpleExpr LikeEscapeOpt %prec LIKE {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_LIKE, $1, $3, $4, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | SimpleExpr EXACT_LIKE SimpleExpr LikeEscapeOpt %prec LIKE {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_EXACT_LIKE, $1, $3, $4, parser->arena);
        fun->location = parser->get_location(@1, @$);
        $$ = fun;
    }
    | SimpleExpr RegexpOrNot SimpleExpr %prec LIKE {
        FuncExpr* fun = FuncExpr::new_binary_op_node(cantor::pb::ast::FT_REGEXP, $1, $3, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    | SimpleExpr BetweenOrNot SimpleExpr AND SimpleExpr {
        FuncExpr* fun = FuncExpr::new_ternary_op_node(cantor::pb::ast::FT_BETWEEN, $1, $3, $5, parser->arena);
        fun->location = parser->get_location(@1, @$);
        fun->is_not = $2;
        $$ = fun;
    }
    ;
IsOrNot:
    IS {
        $$ = false;
    }
    | IS NOT {
        $$ = true;
    }
    ;
InOrNot:
    IN {
        $$ = false;
    }
    | NOT IN {
        $$ = true;
    }
    ;
AnyOrAll:
    ANY {
        $$ = cantor::pb::ast::CMP_ANY;
    }
    | SOME {
        $$ = cantor::pb::ast::CMP_SOME;
    }
    | ALL {
        $$ = cantor::pb::ast::CMP_ALL;
    }
    ;
LikeOrNot:
    LIKE {
        $$ = false;
    }
    | NOT LIKE {
        $$ = true;
    }
    ;
LikeEscapeOpt:
    {
        $$ = ConstantExpr::make_string("'\\'", parser->arena);
    }
    |   "ESCAPE" STRING_LIT {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    ;
RegexpOrNot:
    REGEXP {
        $$ = false;
    }
    | RLIKE {
        $$ = false;
    }
    | NOT REGEXP {
        $$ = true;
    }
    | NOT RLIKE {
        $$ = true;
    }
    ;
BetweenOrNot:
    BETWEEN {
        $$ = false;
    }
    | NOT BETWEEN {
        $$ = true;
    }
    ;


/*create table statement*/
// TODO: create table xx like xx
CreateTableStmt:
    CREATE TABLE IfNotExists TableName '(' TableElementListOpt ')' CreateTableOptionListOpt
    {
        CreateTableStmt* stmt = new_node(CreateTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_not_exist = $3;
        stmt->table_name = $4;
        stmt->set_def_element($6, parser->arena);
        set_vector(stmt->options, $8);
        $$ = stmt;
    }
    ;

IfNotExists:
    {
        $$ = false;
    }
    | IF NOT EXISTS
    {
        $$ = true;
    }
    ;

IfExists:
    {
        $$ = false;
    }
    | IF EXISTS
    {
        $$ = true;
    }
    ;

TableElementListOpt:
    {
        $$ = nullptr;
    }
    | TableElementList 
    {
        $$ = $1;
    }

TableElementList:
    TableElement
    {
        Vector<TableDefElement*>* list;
        if ($1 != nullptr) {
            list = append_vector<TableDefElement*>(nullptr, $1, parser->arena, 10);
        } else {
            list = append_vector<TableDefElement*>(nullptr,parser->arena, 10);
        }
        $$ = list;
    }
    | TableElementList ',' TableElement
    {
        if ($3 != nullptr) {
            $1->push_back($3, parser->arena);
        }
        $$ = $1;
    }
    ;

TableElement:
    ColumnDef
    {
        auto n = new_node(TableDefElement);
        n->location = parser->get_location(@1);
        n->column = $1;
        $$ = n;
    }
    | Constraint
    {
        auto n = new_node(TableDefElement);
        n->location = parser->get_location(@1);
        n->constrain = $1;
        $$ = n;
    }
    ;

ColumnDef:
    ColumnName TypeStmt ColumnOptionList
    {
        ColumnDef* column = new_node(ColumnDef);
        column->location = parser->get_location(@1, @$);
        column->name = $1;
        column->type_name = $2;
        set_vector(column->options, $3);
        $$ = column;
    }
    ;

ColumnDefList:
    ColumnDef
    {
        Vector<ColumnDef*>* vec;
        if ($1 != nullptr) {
            vec = append_vector<ColumnDef*>(nullptr, $1, parser->arena);
        } else {
            vec = append_vector<ColumnDef*>(nullptr, parser->arena);
        }
        $$ = vec;
    }
    | ColumnDefList ',' ColumnDef
    {
        if ($3 != nullptr) {
            append_vector<>($1, $3, parser->arena);
        }
        $$ = $1;
    }
    ;

ColumnOptionList:
    {
        /// guard that ColumnOptionList cnot nullptr
        //$$ = append_vector<ColumnOption*>(nullptr, parser->arena);
        $$ = nullptr;
    }
    | ColumnOption
    {
         $$ = append_vector<ColumnOption*>(nullptr, $1, parser->arena);
    }
    | ColumnOptionList ColumnOption
    {
        $$ = append_vector<ColumnOption*>($1, $2, parser->arena);
    }
    ;

ColumnOption:
    NOT NULLX
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_NOT_NULL;
        $$ = option;
    }
    | NULLX
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1);
        option->type = cantor::pb::ast::COLUMN_OPT_NULL;
        $$ = option;
    }
    | AUTO_INCREMENT
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1);
        option->type = cantor::pb::ast::COLUMN_OPT_AUTO_INC;
        $$ = option;
    }
    | PrimaryOpt KEY
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_PRIMARY_KEY;
        $$ = option;
    }
    | UNIQUE %prec lowerThanKey
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_UNIQ_KEY;
        $$ = option;
    }
    | UNIQUE KEY
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_UNIQ_KEY;
        $$ = option;
    }
    | DEFAULT DefaultValue
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_DEFAULT_VAL;
        option->expr = $2;
        $$ = option;
    }
    | CHECK '(' Expr ')'
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_CHECK;
        option->expr = $3;
        $$ = option;
    }
    | ON UPDATE FunctionCallCurTimestamp
    {
        FuncExpr* current_timestamp = new_node(FuncExpr);
        current_timestamp->location = parser->get_location(@3);
        current_timestamp->func_type = cantor::pb::ast::FT_COMMON;
        current_timestamp->fn_name = "current_timestamp";
        ColumnOption* option = new_node(ColumnOption);
        option->type = cantor::pb::ast::COLUMN_OPT_ON_UPDATE;
        option->expr = current_timestamp;
        option->location = parser->get_location(@1, @$);
        $$ = option;
    }
    | COMMENT STRING_LIT
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_COMMENT;
        $2->location = parser->get_location(@2);
        option->expr = $2;
        $$ = option;
    }
    | COLLATE StringName
    {
        ColumnOption* option = new_node(ColumnOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::COLUMN_OPT_COLLATE;
        option->expr = ConstantExpr::make_string($2, parser->arena);
        option->expr->location = parser->get_location(@2);
        $$ = option;
    }
    ;
    
SignedLiteral:
    Literal {}
    | '+' NumLiteral {
        $$ = $2;
        $$->location = parser->get_location(@1, @$);
    }
    | '-' NumLiteral
    {
        ConstantExpr* literal = (ConstantExpr*)$2;
        if (literal->literal_type == cantor::pb::ast::CET_INT) {
            literal->_u.int64_val = 0 - literal->_u.int64_val;
        } else {
            literal->_u.double_val = 0 - literal->_u.double_val;
        }
        $$ = literal;
        $$->location = parser->get_location(@1, @$);
    }
    ;

DefaultValue:
    FunctionCallCurTimestamp
    {
        FuncExpr* current_timestamp = new_node(FuncExpr);
        current_timestamp->location = parser->get_location(@1);
        current_timestamp->func_type = cantor::pb::ast::FT_COMMON;
        current_timestamp->fn_name = "current_timestamp";
        $$ = (DispatchExpr*)current_timestamp;
    }
    | SignedLiteral
    {
        $$ = $1;
    }

FunctionCallCurTimestamp:
    NOW '(' ')'
    | NOW '(' NumLiteral ')'
    | FunctionNameCurTimestamp
    | FunctionNameCurTimestamp '(' ')'
    | FunctionNameCurTimestamp '(' NumLiteral ')'
    ;
FunctionNameCurTimestamp:
    CURRENT_TIMESTAMP 
    | LOCALTIME 
    | LOCALTIMESTAMP 
    ;

PrimaryOpt:
    {}
    | PRIMARY
    ;

DefaultKwdOpt:
    {}
    | DEFAULT
    ;

Constraint:
    ConstraintKeywordOpt ConstraintElem
    {
        if (!$1.empty()) {
            ((Constraint*)$2)->name = $1;
        }
        $$ = $2;
    }
    ;

ConstraintKeywordOpt:
    {
        $$ = nullptr;
    }
    | CONSTRAINT
    {
        $$ = nullptr;
    }
    | CONSTRAINT AllIdent
    {
        $$ = $2;
    }
    ;

ConstraintElem:
    PRIMARY KEY IndexTypeOpt '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_PRIMARY;
        set_vector(item->columns, $5);
        auto op = set_index_type($7, $3, parser->arena);
        item->index_option = op;
        $$ = item;
    }
    | FULLTEXT KeyOrIndexOpt IndexName '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_FULLTEXT;
        item->name = $3;
        set_vector(item->columns, $5);
        item->index_option = $7;
        $$ = item;
    }
    | VECTOR KeyOrIndexOpt IndexName '(' ColumnNameList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_VECTOR;
        item->name = $3;
        set_vector(item->columns, $5);
        item->index_option = $7;
        $$ = item;
    }
    | KeyOrIndex IndexName IndexTypeOpt '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_INDEX;
        item->name = $2;
        set_vector(item->columns, $5);
        auto op = set_index_type($7, $3, parser->arena);
        item->index_option = op;
        $$ = item;
    }
    | UNIQUE KeyOrIndexOpt IndexName IndexTypeOpt '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_UNIQ;
        item->name = $3;
        set_vector(item->columns, $6);
        auto op = set_index_type($8, $4, parser->arena);
        item->index_option = op;
        $$ = item;
    }
    | KeyOrIndex GlobalOrLocal IndexName IndexTypeOpt '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_INDEX;
        item->index_dist = $2;
        item->name = $3;
        set_vector(item->columns, $6);
        auto op = set_index_type($8, $4, parser->arena);
        item->index_option = op;
        $$ = item;
    }
    | UNIQUE KeyOrIndexOpt GlobalOrLocal IndexName IndexTypeOpt '(' IndexColumnList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_UNIQ;
        item->index_dist = $3;
        item->name = $4;
        set_vector(item->columns, $7);
        auto op = set_index_type($9, $5, parser->arena);
        item->index_option = op;
        $$ = item;
    }
    | ROLLUP KeyOrIndexOpt IndexName '(' ColumnNameList ')' IndexOptionList
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_ROLLUP;
        item->name = $3;
        set_vector(item->columns, $5);
        item->index_option = $7;
        $$ = item;
    }
    | CHECK '(' Expr ')'
    {
        auto* item = new_node(Constraint);
        item->location = parser->get_location(@1, @$);
        item->type = cantor::pb::ast::CONSTRAINT_CHECK;
        item->check_expr = $3;
        $$ = item;
    }
    ;

GlobalOrLocal:
    GLOBAL
    {
        $$ = cantor::pb::ast::INDEX_DIST_GLOBAL;
    }
    | LOCAL
    {
        $$ = cantor::pb::ast::INDEX_DIST_LOCAL;
    }
    ;

GlobalOrLocalOpt:
    {
        $$ = cantor::pb::ast::INDEX_DIST_DEFAULT;
    }
    | GlobalOrLocal
    {
        $$ = $1;
    }
    ;

IndexName:
    AllIdent
    {
        $$ = $1;
    }
    ;

IndexOptionList:
    {
        $$ = nullptr;
    }
    | IndexOptionList IndexOption {
        auto node  = merge_index_option($1, $2);
        node->location = parser->get_location(@1, @$);
        $$ = node;
    }
    ;

AlgorithmOpt:
    ALGORITHM EqOpt AlgorithmVal {
        auto node = new_node(AlgorithmOpt);
        node->location = parser->get_location(@1, @$);
        node->algo = $3;
        $$ = node;
    }
    ;

IndexLockOpt:
    LOCK EqOpt IndexLockVal
    {
        auto node = new_node(IndexLockOpt);
        node->location = parser->get_location(@1, @$);
        node->lock_mode = $3;
        $$ = node;
    }
    ;

DropIndexOptNoneUsed:
    {
        $$ = nullptr;
    }
    | AlgorithmOpt
    {

       $$ = nullptr;
    }
    | IndexLockOpt
    {
        $$ = nullptr;
    }
    ;

IndexOption:
    KEY_BLOCK_SIZE EqOpt INTEGER_LIT {
        $$ = nullptr;
    }
    | IndexType {
        IndexOption* op = new_node(IndexOption);
        op->location = parser->get_location(@1);
        op->index_type = $1;
        $$ = op;
    }
    | COMMENT STRING_LIT {
        IndexOption* op = new_node(IndexOption);
        op->location = parser->get_location(@1, @$);
        op->comment = ((ConstantExpr*)$2)->_u.str_val;
        $$ = op;
    }
    | AlgorithmOpt {

        IndexOption* op = new_node(IndexOption);
        op->location = parser->get_location(@1);
        op->algo = $1;
        $$ = op;
    }
    | IndexLockOpt
    {
        IndexOption* op = new_node(IndexOption);
        op->location = parser->get_location(@1);
        op->lock = $1;
        $$ = op;
    }
    ;

IndexType:
    USING BTREE {
        $$ = cantor::pb::ast::INDEX_BTREE;
    }
    | USING HASH {
        $$ = cantor::pb::ast::INDEX_HASH;
    }
    ;

IndexTypeOpt:
    {
        $$ = cantor::pb::ast::INDEX_NONE;
    }
    | IndexType {
        $$ = $1;
    };

CharsetKw:
    CHARACTER SET 
    | CHARSET
    ;


CreateTableOptionListOpt:
    {
        $$ = nullptr;
    }
    | TableOptionList 
    {
        $$ = $1;
    }
    ;

TableOptionList:
    TableOption
    {
        $$ = append_vector<TableOption*>(nullptr, $1, parser->arena, 10);
    }
    | TableOptionList TableOption
    {
        if ($2 != nullptr) {
            $1->push_back($2, parser->arena);
        }
        $$ = $1;
    }
    | TableOptionList ','  TableOption
    {
        if ($3 != nullptr) {
            $1->push_back($3, parser->arena);
        }
        $$ = $1;
    }
    ;

TableOption:
    ENGINE EqOpt StringName
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_ENGINE;
        option->str_value = $3;
        $$ = option;
    }
    | DefaultKwdOpt CharsetKw EqOpt StringName
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_CHARSET;
        option->str_value = $4;
        $$ = option;
    }
    | DefaultKwdOpt COLLATE EqOpt StringName
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_COLLATE;
        option->str_value = $4;
        $$ = option;
    }
    | AUTO_INCREMENT EqOpt INTEGER_LIT
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_AUTO_INC;
        option->uint_value = ((ConstantExpr*)$3)->_u.int64_val;
        $$ = option;
    }
    | COMMENT EqOpt STRING_LIT
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_COMMENT;
        option->str_value = ((ConstantExpr*)$3)->_u.str_val;
        $$ = option;
    }
    | AVG_ROW_LENGTH EqOpt INTEGER_LIT
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_AVG_ROW_LENGTH;
        option->uint_value = ((ConstantExpr*)$3)->_u.int64_val;
        $$ = option;
    }
    | KEY_BLOCK_SIZE EqOpt INTEGER_LIT
    {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_KEY_BLOCK_SIZE;
        option->uint_value = ((ConstantExpr*)$3)->_u.int64_val;
        $$ = option;
    }
    | DYNAMIC_PARTITION_ATTR EqOpt STRING_LIT {
        TableOption* option = new_node(TableOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_DYNAMIC_PARTITION_ATTR;
        option->str_value = ((ConstantExpr*)$3)->_u.str_val;
        $$ = option;
    }
    | PARTITION BY TablePartitionOpt
    {
        TableOption* option = new_node(TableOption);
        option->partition = $3;
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::TABLE_OPT_PARTITION;
        $$ = option;
    }
    | IgnoreTableOption
    {
        $$ = nullptr;
    }
    ;

PartitionOption:
    COMMENT EqOpt STRING_LIT
    {
        PartitionOption* option = new_node(PartitionOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::PARTITION_OPT_COMMENT;
        option->str_value = ((ConstantExpr*)$3)->_u.str_val;
        $$ = option;
    }
    ;

PartitionOptionList:
    PartitionOption
    {
        $$ = append_vector<PartitionOption*>(nullptr, $1, parser->arena, 10);
    }
    | PartitionOptionList PartitionOption
    {
        $1->push_back($2, parser->arena);
        $$ = $1;
    }
    ;

PartitionOptionListOpt:
    {
        $$ = nullptr;
    }
    | PartitionOptionList {
        $$ = $1;
    }
    ;

PartitionRange:
    PARTITION AllIdent VALUES LESS THAN '(' Expr ')' PartitionOptionListOpt
    {
        PartitionRange* p = new_node(PartitionRange);
        p->location = parser->get_location(@1, @$);
        p->name = $2;
        p->less_expr = $7;
        set_vector(p->options, $9);
        $$ = p;
    }
    | PARTITION AllIdent VALUES LESS THAN MAXVALUE PartitionOptionListOpt
    {
        PartitionRange* p = new_node(PartitionRange);
        p->location = parser->get_location(@1, @$);
        p->name = $2;
        p->less_expr = ConstantExpr::make_maxvalue(parser->arena);
        set_vector(p->options, $7);
        $$ = p;
    }
    | PARTITION AllIdent VALUES '[' Expr ',' Expr ')' PartitionOptionListOpt
    {
        PartitionRange* p = new_node(PartitionRange);
        p->location = parser->get_location(@1, @$);
        p->name = $2;
        p->range = std::make_pair($5, $7);
        set_vector(p->options, $9);
        $$ = p;
    }
    | PARTITION AllIdent VALUES '[' Expr ',' MAXVALUE ')' PartitionOptionListOpt
    {
        PartitionRange* p = new_node(PartitionRange);
        p->location = parser->get_location(@1, @$);
        p->name = $2;
        p->range = std::make_pair($5, ConstantExpr::make_maxvalue(parser->arena));
        set_vector(p->options, $9);
        $$ = p;
    }
    ;

PartitionRangeList:
    PartitionRange
    {
        $$ = append_vector<PartitionRange*>(nullptr, $1, parser->arena, 10);
    }
    | PartitionRangeList ',' PartitionRange
    {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

PartitionRangeListOpt:
    {
        $$ = nullptr;
    }
    | PartitionRangeList
    {
        $$ = $1;
    }
    ;
InsertMethodOpt:
    NO { $$ = 0; }
    | FIRST { $$ = 0; }
    | LAST { $$ = 0; }
    ;

RowFormatOpt:
    DEFAULT
    {
        $$ = 0;
    }
    | DYNAMIC
    {
        $$ = 0;
    }
    | FIXED
    {
         $$ = 0;
    }
    | COMPRESSED
    {
        $$ = 0;
    }
    | REDUNDANT
    {
        $$ = 0;
    }
    | COMPACT
    {
        $$ = 0;
    }
    ;

AlgorithmVal:
    DEFAULT
    {
        $$ = ConstantExpr::make_string("DEFAULT", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | INSTANT
    {
         $$ = ConstantExpr::make_string("INSTANT", parser->arena);
         $$->location = parser->get_location(@1);
    }
    | INPLACE
    {
         $$ = ConstantExpr::make_string("INPLACE", parser->arena);
         $$->location = parser->get_location(@1);
    }
    | COPY
    {
        $$ = ConstantExpr::make_string("COPY", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | STRING_LIT
    {
        $$ = $1;
        $$->location = parser->get_location(@1);
    }
    ;

IndexLockVal:
    DEFAULT
    {
        $$ = ConstantExpr::make_string("DEFAULT", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | NONE
    {
        $$ = ConstantExpr::make_string("NONE", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | SHARED
    {
        $$ = ConstantExpr::make_string("SHARED", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | EXCLUSIVE
    {
        $$ = ConstantExpr::make_string("EXCLUSIVE", parser->arena);
        $$->location = parser->get_location(@1);
    }
    | STRING_LIT
    {
          $1->location = parser->get_location(@1);
          $$ = $1;
    }
    ;

IgnoreTableOption:
    ROW_FORMAT EqOpt RowFormatOpt
    | STATS_PERSISTENT EqOpt NumLiteral
    | STATS_PERSISTENT EqOpt DEFAULT
    | STATS_AUTO_RECALC EqOpt NumLiteral
    | STATS_AUTO_RECALC EqOpt DEFAULT
    | STATS_SAMPLE_PAGES EqOpt NumLiteral
    | CHECKSUM EqOpt NumLiteral
    | MAX_ROWS EqOpt NumLiteral
    | MIN_ROWS EqOpt NumLiteral
    | PACK_KEYS EqOpt NumLiteral
    | PACK_KEYS EqOpt DEFAULT
    | ENCRYPTION EqOpt STRING_LIT
    | ENGINE_ATTRIBUTE EqOpt STRING_LIT
    | SECONDARY_ENGINE_ATTRIBUTE EqOpt STRING_LIT
    | INSERT_METHOD EqOpt InsertMethodOpt
    | DELAY_KEY_WRITE EqOpt NumLiteral
    | CONNECTION EqOpt STRING_LIT
    | COMPRESSION EqOpt STRING_LIT
    | AUTOEXTEND_SIZE EqOpt NumLiteral
    ;

TablePartitionOpt:
    RANGE '(' Expr ')' '(' PartitionRangeListOpt ')'
    {
        TablePartitionOption* option = new_node(TablePartitionOption);
        option->location = parser->get_location(@1, @$);
        option->partition_type = cantor::pb::ast::PARTITION_RANGE;
        option->expr = $3;
        set_vector(option->ranges, $6);
        $$ = option;
    }
    | HASH '(' Expr ')' PARTITIONS INTEGER_LIT 
    {
        TablePartitionOption* option = new_node(TablePartitionOption);
        option->location = parser->get_location(@1, @$);
        option->partition_type = cantor::pb::ast::PARTITION_HASH;
        option->expr = $3;
        option->partition_num = ((ConstantExpr*)$6)->_u.int64_val;
        $$ = option;
    }
    ;

EqOpt:
    {}
    | EQ_OP
    ;

CreateViewStmt:
    CREATE OrReplace VIEW ViewName ViewFieldList AS ViewSelectStmt
    {
        CreateViewStmt* stmt = new_node(CreateViewStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->or_replace = $2;
        stmt->view_name = $4;
        set_vector(stmt->column_names, $5);
        stmt->view_select_stmt = $7;
        $$ = stmt;
    }
    ;

OrReplace:
    /* EMPTY */
    {
        $$ = false;
    }
    | OR REPLACE
    {
        $$ = true;
    }
    ;

ViewFieldList:
    /* Empty */
    {
        $$ = nullptr;
    }
    | '(' ColumnNameListOpt ')'
    {
        $$ = $2;
    }
    ;

ViewSelectStmt:
    SelectStmt
    | UnionStmt
    ;

SelectStmtWithClause:
    WithClause SelectStmt
    {
        SelectStmt* select = (SelectStmt*)$2;
        select->location = parser->get_location(@1, @$);
        select->with = (cantor::parser::WithClause*)$1;
        $$ = select;
    }
    | WithClause UnionStmt
    {
        UnionStmt* union_stmt = (UnionStmt*)$2;
        union_stmt->location = parser->get_location(@1, @$);
        for (int i = 0; i < union_stmt->select_stmts.size(); ++i) {
            union_stmt->select_stmts[i]->with = (cantor::parser::WithClause*)$1;
        }
        $$ = union_stmt;
    }
    ;

WithClause:
    WITH OptTagOption WithList
    {
        $3->location = parser->get_location(@1, @$);
        $3->option = $2;
        $$ = $3;
    }
    ;

WithList:
    WithList ',' CommonTableExpr
    {
        WithClause* with_clause =  (WithClause*) $1;
        with_clause->location = parser->get_location(@1, @$);
        with_clause->ctes.push_back((cantor::parser::CommonTableExpr*)$3, parser->arena);
        $$ = with_clause;
    }
    | CommonTableExpr
    {
        WithClause* with_clause = new_node(WithClause);
        with_clause->location = parser->get_location(@1, @$);
        with_clause->ctes.push_back((cantor::parser::CommonTableExpr*)$1, parser->arena);
        $$ = with_clause;
    }
    ;

CommonTableExpr:
    AllIdent OptTagOption RangeVarColumnNameListOpt AS SubSelect
    {
        CommonTableExpr* cte = new_node(CommonTableExpr);
        cte->location = parser->get_location(@1, @$);
        cte->name = $1;
        cte->option = $2;
        set_vector(cte->column_names, $3);
        cte->query_expr = (SubqueryExpr*)$5;
        $$ = cte;
    }
    ;

OptTagOption:
        {
            $$ = nullptr;
        }
        |   OPTION '(' STRING_LIT ')'
        {
            $$ =  ((ConstantExpr*)$3)->_u.str_val;
        }
        ;

// Drop Table(s) Statement
DropTableStmt:
    DROP TableOrTables IfExists TableNameList RestrictOrCascadeOpt
    {
        DropTableStmt* stmt = new_node(DropTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exist = $3;
        set_vector(stmt->table_names, $4);
        $$ = stmt;
    }
    ;

TableOrTables: 
    TABLE | TABLES
    ;

RestrictOrCascadeOpt:
    {}
    | RESTRICT
    | CASCADE
    ;

// Drop View Statement
DropViewStmt:
    DROP VIEW IfExists ViewName RestrictOrCascadeOpt
    {
        DropViewStmt* stmt = new_node(DropViewStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exist = $3;
        stmt->view_name = $4;
        $$ = stmt;
    }
    ;

// Restore Table(s) Statement
RestoreTableStmt:
    RESTORE TableOrTables TableNameList 
    {
        RestoreTableStmt* stmt = new_node(RestoreTableStmt);
        stmt->location = parser->get_location(@1, @$);
        set_vector(stmt->table_names, $3);
        $$ = stmt;
    }
    ;

// Create Database Statement
CreateDatabaseStmt:
    CREATE DATABASE IfNotExists DBName DatabaseOptionListOpt
    {
        CreateDatabaseStmt* stmt = new_node(CreateDatabaseStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_not_exist = $3;
        stmt->db_name = $4;
        set_vector(stmt->options, $5);
        $$ = stmt;
    }
    ;

DBName:
    AllIdent {
        $$ = $1;
    }
    ;

ResourceTag:
    AllIdent {
        $$ = $1;
    }
    ;
DatabaseOption:
    DefaultKwdOpt CharsetKw EqOpt StringName
    {
        DatabaseOption* option = new_node(DatabaseOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::DATABASE_OPT_CHARSET;
        option->str_value = $4;
        $$ = option;
    }
    | DefaultKwdOpt COLLATE EqOpt StringName
    {
        DatabaseOption* option = new_node(DatabaseOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::DATABASE_OPT_COLLATE;
        option->str_value = $4;
        $$ = option;
    }
    | COMMENT EqOpt STRING_LIT
    {
        DatabaseOption* option = new_node(DatabaseOption);
        option->location = parser->get_location(@1, @$);
        option->type = cantor::pb::ast::DATABASE_OPT_COMMENT;
        option->str_value = ((ConstantExpr*)$3)->_u.str_val;
        $$ = option;
    }
    ;

DatabaseOptionListOpt:
    {
        $$ = nullptr;
    }
    | DatabaseOptionList
    {
        $$ = $1;
    }
    ;

DatabaseOptionList:
    DatabaseOption
    {
        $$ = append_vector<DatabaseOption*>(nullptr, $1, parser->arena);
    }
    | DatabaseOptionList DatabaseOption
    {
        $1->push_back($2, parser->arena);
        $$ = $1;
    }
    ;

DropDatabaseStmt:
    DROP DATABASE IfExists DBName
    {
        DropDatabaseStmt* stmt = new_node(DropDatabaseStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exist = $3;
        stmt->db_name = $4;
        $$ = stmt;
    }
    ;

WorkOpt:
    {}
    | WORK
    ;

StartTransactionStmt:
    TXN START READ ONLY WorkOpt
    {
        auto node = new_node(StartTxnStmt);
        node->read_only = true;
        node->location = parser->get_location(@1, @$);
        $$ = node;
    }
    | TXN START WorkOpt
    {
        $$ = new_node(StartTxnStmt);
        $$->location = parser->get_location(@1, @$);
    }
    ;

CommitTransactionStmt:
    TXN COMMIT WorkOpt
    {
        $$ = new_node(CommitTxnStmt);
        $$->location = parser->get_location(@1, @$);
    }
    ;

RollbackTransactionStmt:
    TXN ROLLBACK WorkOpt
    {
        $$ = new_node(RollbackTxnStmt);
        $$->location = parser->get_location(@1, @$);
    }
    ;

PragmaSetStmt:
    PRAGMA SET VarAssignList
    {
        $$ = $3;
        $$->location = parser->get_location(@1, @$);
    }
    | PRAGMA SET GLOBAL TRANSACTION TransactionChars
    {
        $$ = $5;
        $$->location = parser->get_location(@1, @$);
    }
    | PRAGMA SET SESSION TRANSACTION TransactionChars
    {
        $$ = $5;
        $$->location = parser->get_location(@1, @$);
    }
    | PRAGMA SET TRANSACTION TransactionChars
    {
        $$ = $4;
        $$->location = parser->get_location(@1, @$);
    }
    ;

VarAssignList:
    VarAssignItem
    {   
        auto* set = new_node(PragmaSetStmt);
        set->location = parser->get_location(@1);
        set->var_list.push_back($1, parser->arena);
        $$ = set;
    }
    | VarAssignList ',' VarAssignItem
    {
        ((PragmaSetStmt*)$1)->var_list.push_back($3, parser->arena);
        $$ = $1;
        $$->location = parser->get_location(@1, @$);
    }
    ;

VarAssignItem:
    VarName EQ_OP Expr
    {
        auto assign = new_node(VarAssign);
        assign->location = parser->get_location(@1, @$);
        assign->key = $1;
        if ($3 != nullptr && $3->expr_type == cantor::pb::ast::ENT_RANGE_VAR) {
            auto* col_name = (RangeVar*)$3;
            if (col_name->column && col_name->column->column_name.to_lower() == "off") {
                // SET XXX = OFF
                // OFF to 0
                assign->value = ConstantExpr::make_int("0", parser->arena);
            } else {
                assign->value = $3;
            }
        } else {
            assign->value = $3;
        }
        $$ = assign;
    }
    | VarName EQ_OP ON
    {
        auto assign = new_node(VarAssign);
        assign->location = parser->get_location(@1, @$);
        assign->key = $1;
        assign->value = ConstantExpr::make_int("1", parser->arena);
        $$ = assign;
    }
    | VarName EQ_OP DEFAULT
    {
        auto assign = new_node(VarAssign);
        assign->location = parser->get_location(@1, @$);
        assign->key = $1;
        $$ = assign;
    }
    | VarName ASSIGN_OP Expr
    {
        auto assign = new_node(VarAssign);
        assign->location = parser->get_location(@1, @$);
        assign->key = $1;
        if ($3 != nullptr && $3->expr_type == cantor::pb::ast::ENT_RANGE_VAR) {
            auto* col_name = (RangeVar*)$3;
            if (col_name->column && col_name->column->column_name.to_lower() == "off") {
                // SET XXX = OFF
                // OFF to 0
                assign->value = ConstantExpr::make_int("0", parser->arena);
            } else {
                assign->value = $3;
            }
        } else {
            assign->value = $3;
        }
        $$ = assign;
    }
    | CharsetKw AllIdent
    {
        VarAssign* assign = nullptr;
        if ($2.empty() == false) {
            assign = new_node(VarAssign);
            assign->key.strdup("CHARACTER SET", parser->arena);
            assign->value = ConstantExpr::make_string($2.value, parser->arena);
            assign->location = parser->get_location(@1, @$);
        }
        $$ = assign;
    }
    ;

VarName:
    AllIdent
    {
        $$ = $1;
    }
    | GLOBAL AllIdent
    {
        String str;
        if ($2.empty() == false) {
            str.strdup("@@global.", parser->arena);
            str.append($2.c_str(), parser->arena);
        }
        $$ = str;
    }
    | SESSION AllIdent
    {
        String str;
        if ($2.empty() == false) {
            str.strdup("@@session.", parser->arena);
            str.append($2.c_str(), parser->arena);
        }
        $$ = str;
    }
    | LOCAL AllIdent
    {
        String str;
        if ($2.empty() == false) {
            str.strdup("@@local.", parser->arena);
            str.append($2.c_str(), parser->arena);
        }
        $$ = str;
    }
    ;

TransactionChars:
    TransactionChar
    {
        auto* set = new_node(PragmaSetStmt);
        set->location = parser->get_location(@1);
        set->var_list.push_back($1, parser->arena);
        $$ = set;
    }
    | TransactionChars ',' TransactionChar 
    {
        ((PragmaSetStmt*)$1)->var_list.push_back($3, parser->arena);
        $$ = $1;
        $$->location = parser->get_location(@1, @$);
    }
    ;

TransactionChar: 
    ISOLATION LEVEL IsolationLevel 
    {
        auto assign = new_node(VarAssign);
        assign->location = parser->get_location(@1, @$);
        assign->key.strdup("@@isolation.", parser->arena);
        assign->value = $3;
        $$ = assign;
    }
    ;
//    | READ WRITE
//    {
//    }
//    | READ ONLY
//    {
//    }
//    ;

IsolationLevel:
    REPEATABLE READ
    {
        $$ = ConstantExpr::make_int("1", parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | READ COMMITTED
    {
        $$ = ConstantExpr::make_int("2", parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | READ UNCOMMITTED
    {
        $$ = ConstantExpr::make_int("3", parser->arena);
        $$->location = parser->get_location(@1, @$);
    }
    | SERIALIZABLE
    {
        $$ = ConstantExpr::make_int("4", parser->arena);
        $$->location = parser->get_location(@1);
    }
    ;

ShowStmt:
    SHOW ShowTargetFilterable ShowLikeOrWhereOpt {
        $$ = nullptr;
    }
    | SHOW CREATE TABLE TableName {
        $$ = nullptr;
    }
    | SHOW CREATE VIEW ViewName {
        $$ = nullptr;
    }
    | SHOW CREATE DATABASE DBName {
        $$ = nullptr;
    }
    | SHOW GRANTS {
        // See https://dev.mysql.com/doc/refman/5.7/en/show-grants.html
        $$ = nullptr;
    }
/*
    | SHOW GRANTS FOR Username {
        $$ = nullptr;
    }
*/
    | SHOW MASTER STATUS {
        $$ = nullptr;
    }
    | SHOW OptFull PROCESSLIST {
        $$ = nullptr;
    }
    | SHOW PROFILES {
        $$ = nullptr;
    }
    | SHOW PRIVILEGES {
        $$ = nullptr;
    }
    ;
ShowIndexKwd:
    INDEX
    | INDEXES
    | KEYS
    ;

FromOrIn:
    FROM | IN

ShowTargetFilterable:
    ENGINES {
        $$ = nullptr;
    }
    | DATABASES {
        $$ = nullptr;
    }
    | CharsetKw {
        $$ = nullptr;
    }
    | OptFull TABLES ShowDatabaseNameOpt {
        $$ = nullptr;
    }
    | TABLE STATUS ShowDatabaseNameOpt {
        $$ = nullptr;
    }
    | ShowIndexKwd FromOrIn TableName {
        $$ = nullptr;
    }

    | ShowIndexKwd FromOrIn AllIdent FromOrIn AllIdent {
        $$ = nullptr;
    }

    | OptFull COLUMNS ShowTableAliasOpt ShowDatabaseNameOpt {
        $$ = nullptr;
    }
    | OptFull FIELDS ShowTableAliasOpt ShowDatabaseNameOpt {
        // SHOW FIELDS is a synonym for SHOW COLUMNS
        $$ = nullptr;
    }
    | WARNINGS {
        $$ = nullptr;
    }
    | GlobalScope VARIABLES {
        $$ = nullptr;
    }
    | GlobalScope STATUS {
        $$ = nullptr;
    }
    | COLLATION {
        $$ = nullptr;
    }
    | TRIGGERS ShowDatabaseNameOpt {
        $$ = nullptr;
    }
    | PROCEDURE STATUS {
        $$ = nullptr;
    }
    | FUNCTION STATUS
    {
        $$ = nullptr;
    }
    | EVENTS ShowDatabaseNameOpt
    {
        $$ = nullptr;
    }
    | PLUGINS {
        $$ = nullptr;
    }
    ;
ShowLikeOrWhereOpt: {
        $$ = nullptr;
    }
    | LIKE SimpleExpr {
        $$ = nullptr;
    }
    | WHERE Expr {
        $$ = nullptr;
    }
    ;
GlobalScope:
    {
        $$ = false;
    }
    | GLOBAL {
        $$ = true;
    }
    | SESSION {
        $$ = false;
    }
    ;
OptFull:
    {
        $$ = false;
    }
    | FULL {
        $$ = true;
    }
    ;
ShowDatabaseNameOpt:
    {
        $$ = nullptr;
    }
    | FromOrIn DBName {
        $$ = nullptr;
    }
    ;
ShowTableAliasOpt:
    FromOrIn TableName {
        $$ = nullptr;
    }
    ;

AlterTableStmt:
    ALTER IgnoreOptional TABLE TableName AlterSpecList
    {
        AlterTableStmt* stmt = new_node(AlterTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->ignore = $2;
        stmt->table_name = $4;
        set_vector(stmt->alter_specs, $5);
        $$ = stmt;
    }
    | CREATE KeyOrIndex GlobalOrLocalOpt IndexName IndexTypeOpt ON TableName '(' IndexColumnList ')' IndexOptionList
    {
        AlterTableStmt* stmt = new_node(AlterTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->table_name = $7;
        auto* item = new_node(Constraint);
        item->type = cantor::pb::ast::CONSTRAINT_INDEX;
        item->index_dist = $3;
        item->name = $4;
        set_vector(item->columns, $9);
        auto op = set_index_type($11, $5, parser->arena);
        item->index_option = op;
        auto spec = new_node(AlterTableSpec);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_INDEX;
        spec->new_constraints.push_back((Constraint*)item, parser->arena);
        stmt->alter_specs.push_back(spec, parser->arena);
        $$ = stmt;
    }
    | CREATE UNIQUE KeyOrIndexOpt GlobalOrLocalOpt IndexName IndexTypeOpt ON TableName '(' IndexColumnList ')' IndexOptionList
    {
        AlterTableStmt* stmt = new_node(AlterTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->table_name = $8;
        auto* item = new_node(Constraint);
        item->type = cantor::pb::ast::CONSTRAINT_UNIQ;
        item->index_dist = $4;
        item->name = $5;
        set_vector(item->columns, $10);
        auto op = set_index_type($12, $6, parser->arena);
        item->index_option = op;
        auto spec = new_node(AlterTableSpec);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_INDEX;
        spec->new_constraints.push_back((Constraint*)item, parser->arena);
        stmt->alter_specs.push_back(spec, parser->arena);
        $$ = stmt;
    }
    | DROP INDEX IndexName ON TableName ForceOrNot DropIndexOptNoneUsed
    {
        AlterTableStmt* stmt = new_node(AlterTableStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->table_name = $5;
        auto spec = new_node(AlterTableSpec);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_INDEX;
        spec->index_name = $3;
        spec->force = $6;
        stmt->alter_specs.push_back(spec, parser->arena);
        $$ = stmt;
    }
    ;

AlterSpecList:
    AlterSpec
    {
        $$ = append_vector<AlterTableSpec*>(nullptr, $1, parser->arena);
    }
    | AlterSpecList ',' AlterSpec
    {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

ColumnKwdOpt:
    {}
    | COLUMN
    ;

AsOrToOpt:
    {}
    | AS
    {}
    | TO
    {}

ColumnPosOpt:
    {}
    | FIRST
    {}
    | AFTER RangeVar
    {}
    ;

AlterSpec:
    TableOptionList %prec lowerThanComma
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_TABLE_OPTION;
        set_vector(spec->table_options, $1);
        $$ = spec;
    }
    | ADD ColumnKwdOpt ColumnDef ColumnPosOpt
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_COLUMN;
        spec->new_columns.push_back((ColumnDef*)$3, parser->arena);
        $$ = spec;
    }
    | ADD COLUMN UNIQUE ColumnDef ColumnPosOpt
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_COLUMN;
        spec->is_unique_indicator = true;
        spec->new_columns.push_back((ColumnDef*)$4, parser->arena);
        $$ = spec;
    }
    | ADD ColumnKwdOpt '(' ColumnDefList ')'
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_COLUMN;
        set_vector(spec->new_columns, $4);
        $$ = spec;
    }
    | DROP ColumnKwdOpt AllIdent
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_COLUMN;
        spec->column_name = $3;
        $$ = spec;
    }
    | RENAME COLUMN AllIdent TO ColumnName
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_RENAME_COLUMN;
        spec->column_name = $3;

        ColumnDef* new_column = new_node(ColumnDef);
        new_column->name = $5;
        spec->new_columns.push_back(new_column, parser->arena);
        $$ = spec;
    }
    | RENAME AsOrToOpt TableName
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_RENAME_TABLE;
        spec->new_table_name = $3;
        $$ = spec;
    }
    | SWAP AsOrToOpt TableName
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_SWAP_TABLE;
        spec->new_table_name = $3;
        $$ = spec;
    }
    | ADD ConstraintKeywordOpt ConstraintElem
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_INDEX;
        spec->new_constraints.push_back((Constraint*)$3, parser->arena);
        $$ = spec;
    }
    | ADD VIRTUAL ConstraintKeywordOpt ConstraintElem
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_INDEX;
        spec->is_virtual_index = true;
        spec->new_constraints.push_back((Constraint*)$4, parser->arena);
        $$ = spec;
    }
    | DROP KeyOrIndex IndexName ForceOrNot
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_INDEX;
        spec->index_name = $3;
        spec->force = $4;
        $$ = spec;
    }
    | DROP VIRTUAL KeyOrIndex IndexName
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_INDEX;
        spec->is_virtual_index = true;
        spec->index_name = $4;
        $$ = spec;
    }
    | RESTORE KeyOrIndex IndexName
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_RESTORE_INDEX;
        spec->index_name = $3;
        $$ = spec;
    }
    | ADD LEARNER ResourceTag
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_LEARNER;
        spec->resource_tag = $3;
        $$ = spec;
    }
    | DROP LEARNER ResourceTag
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_LEARNER;
        spec->resource_tag = $3;
        $$ = spec;
    }
    | MODIFY COLUMN SET AssignmentList WhereClauseOptional
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_MODIFY_COLUMN;
        set_vector(spec->set_list, $4);
        spec->where = $5;
        $$ = spec;
    }
    | MODIFY ColumnKwdOpt ColumnDef ColumnPosOpt
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_MODIFY_COLUMN;
        spec->new_columns.push_back((ColumnDef*)$3, parser->arena);
        $$ = spec;
    }
    | ADD PartitionRange 
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_ADD_PARTITION;
        spec->partition_range = (PartitionRange*)$2;
        $$ = spec;
    }
    | DROP PARTITION AllIdent 
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_DROP_PARTITION;
        spec->partition_range = new_node(PartitionRange);
        spec->partition_range->name = $3;
        $$ = spec;
    }
    | MODIFY PARTITION AllIdent PartitionOptionListOpt 
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_MODIFY_PARTITION;
        spec->partition_range = new_node(PartitionRange);
        spec->partition_range->name = $3;
        set_vector(spec->partition_range->options, $4);
        $$ = spec;
    }
    | CHANGE ColumnKwdOpt AllIdent ColumnDef ColumnPosOpt
    {
        auto spec = new_node(AlterTableSpec);
        spec->location = parser->get_location(@1, @$);
        spec->spec_type = cantor::pb::ast::ALTER_SPEC_CHANGE_COLUMN;
        spec->column_name = $3;
        spec->new_columns.push_back((ColumnDef*)$4, parser->arena);
        $$ = spec;
    }
    ;

AlterViewStmt:
    ALTER VIEW ViewName ViewFieldList AS ViewSelectStmt
    {
        AlterViewStmt* stmt = new_node(AlterViewStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->view_name = $3;
        set_vector(stmt->column_names, $4);
        stmt->view_select_stmt = $6;
        $$ = stmt;
    }
    ;

// Prepare Statement
NewPrepareStmt:
    PREPARE AllIdent FROM STRING_LIT
    {
        NewPrepareStmt* stmt = new_node(NewPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $2;
        stmt->sql = ((ConstantExpr*)$4)->_u.str_val;
        $$ = stmt;
    }
    | PREPARE AllIdent FROM VarName
    {
        if ($4.starts_with("@@") ) {
            sql_error(&@2, yyscanner, parser, "user variable cannot start with @@");
            return -1;
        }
        if ($4.starts_with("@") == false) {
            sql_error(&@2, yyscanner, parser, "only user variable is permitted in USING clause");
            return -1;
        }
        NewPrepareStmt* stmt = new_node(NewPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $2;
        stmt->sql = $4;
        $$ = stmt;
    }
    ;

ExecPrepareStmt:
    EXECUTE AllIdent
    {
        ExecPrepareStmt* stmt = new_node(ExecPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $2;
        $$ = stmt;
    }
    | EXECUTE AllIdent USING VarList
    {
        ExecPrepareStmt* stmt = new_node(ExecPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $2;
        for (int idx = 0; idx < $4->size(); ++idx) {
            if ((*$4)[idx].starts_with("@@")) {
                sql_error(&@2, yyscanner, parser, "user variable cannot start with @@");
                return -1;
            }
            if ((*$4)[idx].starts_with("@") == false) {
                sql_error(&@2, yyscanner, parser, "only user variable is permitted in USING clause");
                return -1;
            }
            stmt->param_list.push_back((*$4)[idx], parser->arena);
        }
        $$ = stmt;
    }
    ;
    
VarList:
    VarName
    {
        Vector<String>* string_list = new_node(Vector<String>);
        string_list->reserve(5, parser->arena);
        string_list->push_back($1, parser->arena);
        $$ = string_list;        
    }
    | VarList ',' VarName
    {
        $1->push_back($3, parser->arena);
        $$ = $1;
    }
    ;

DeallocPrepareStmt:
    DEALLOCATE PREPARE AllIdent
    {
        DeallocPrepareStmt* stmt = new_node(DeallocPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $3;
        $$ = stmt;
    }
    | DROP PREPARE AllIdent
    {
        DeallocPrepareStmt* stmt = new_node(DeallocPrepareStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->name = $3;
        $$ = stmt;
    }
    ;

ExplainSym:
    EXPLAIN | DESCRIBE | DESC
    ;

ExplainStmt:
    ExplainSym ExplainableStmt {
        ExplainStmt* explain = new_node(ExplainStmt);
        explain->location = parser->get_location(@1, @$);
        explain->stmt = $2;
        explain->format = "row";
        $$ = explain;
    }
    | ExplainSym FORMAT EQ_OP STRING_LIT ExplainableStmt {
        ExplainStmt* explain = new_node(ExplainStmt);
        explain->location = parser->get_location(@1, @$);
        explain->format = ((ConstantExpr*)$4)->_u.str_val;
        explain->stmt = $5;
        $$ = explain;
    }
    ;

KillStmt:
    KILL INTEGER_LIT {
        KillStmt* k = new_node(KillStmt);
        k->location = parser->get_location(@1, @$);
        k->conn_id = ((ConstantExpr*)$2)->_u.int64_val;
        $$ = k;
    }
    | KILL CONNECTION INTEGER_LIT {
        KillStmt* k = new_node(KillStmt);
        k->location = parser->get_location(@1, @$);
        k->conn_id = ((ConstantExpr*)$3)->_u.int64_val;
        $$ = k;
    }
    | KILL QUERY INTEGER_LIT {
        KillStmt* k = new_node(KillStmt);
        k->location = parser->get_location(@1, @$);
        k->conn_id = ((ConstantExpr*)$3)->_u.int64_val;
        k->is_query = true;
        $$ = k;
    }
    ;

/**************************************LoadDataStmt*****************************************
 * See https://dev.mysql.com/doc/refman/5.7/en/load-data.html
 *******************************************************************************************/
LoadDataStmt:
    LOAD DATA LocalOpt INFILE STRING_LIT DuplicateOpt INTO TABLE TableName OptCharset Fields Lines IgnoreLines ColumnNameOrUserVarListOptWithBrackets LoadDataSetSpecOpt
    {
        LoadDataStmt* load = new_node(LoadDataStmt);
        load->location = parser->get_location(@1, @$);
        load->is_local = $3;
        load->path = ((ConstantExpr*)$5)->_u.str_val;
        load->on_duplicate_handle = $6;
        load->table_name = $9;
        load->char_set = $10;
        load->fields_info = (FieldsClause*)$11;
        load->lines_info = (LinesClause*)$12;
        load->ignore_lines = $13;
        set_vector(load->columns, $14);
        set_vector(load->set_list, $15);
        $$ = load;
    }
    ;

// local file
LocalOpt:
    {
        $$ = true;
    }
    |  LOCAL {
        $$ = true;
    }
    ;

IgnoreLines:
    {
        $$ = 0;
    }
    | IGNORE INTEGER_LIT LINES
    {
        $$ = ((ConstantExpr*)$2)->_u.int64_val;
    }
    ;

DuplicateOpt:
    {
        $$ = cantor::pb::ast::ON_DUPLICATE_KEY_ERROR;
    }
    | IGNORE
    {
        $$ = cantor::pb::ast::ON_DUPLICATE_KEY_IGNORE;
    }
    | REPLACE
    {
        $$ = cantor::pb::ast::ON_DUPLICATE_KEY_REPLACE;
    }
    ;

Fields:
    {
        FieldsClause* fields_info = new_node(FieldsClause);
        fields_info->terminated = "\t";
        fields_info->escaped = "\\";
        fields_info->enclosed = nullptr;
        $$ = fields_info;
    }
    | FieldsOrColumns FieldItemList {
        FieldsClause* fields_info = new_node(FieldsClause);
        fields_info->location = parser->get_location(@1, @$);
        fields_info->terminated = "\t";
        fields_info->escaped = "\\";
        fields_info->enclosed = nullptr;
        for (int i = 0; i < $2->size(); i++) {
            FieldItem* item = (*$2)[i];
            switch (item->type) {
                case cantor::pb::ast::LOAD_TERMINATED: {
                    fields_info->terminated = item->value;
                    break;
                }
                case cantor::pb::ast::LOAD_ENCLOSED: {
                    fields_info->enclosed = item->value;
                    fields_info->opt_enclosed = item->opt_enclosed;
                    break;
                }
                case cantor::pb::ast::LOAD_ESCAPED: {
                    fields_info->escaped = item->value;
                    break;
                }
            }
        }
        $$ = fields_info;
    }
    ;

FieldsOrColumns:
    FIELDS
    |  COLUMNS
    ;

FieldItemList:
    FieldItem
    {
        $$ = append_vector<FieldItem*>(nullptr, $1, parser->arena);
    }
    | FieldItemList FieldItem
    {
        $1->push_back($2, parser->arena);
        $$ = $1;
    }
    ;

FieldItem:
    TERMINATED BY STRING_LIT
    {
        FieldItem* field_item = new_node(FieldItem);
        field_item->location = parser->get_location(@1, @$);
        field_item->type = cantor::pb::ast::LOAD_TERMINATED;
        field_item->value = ((ConstantExpr*)$3)->_u.str_val;;
        $$ = field_item;
    }
    | OPTIONALLY ENCLOSED BY STRING_LIT
    {
        FieldItem* field_item = new_node(FieldItem);
        field_item->location = parser->get_location(@1, @$);
        field_item->type = cantor::pb::ast::LOAD_ENCLOSED;
        field_item->value = ((ConstantExpr*)$4)->_u.str_val;;
        field_item->opt_enclosed = true;
        if (field_item->value.to_string() != "\\" && field_item->value.length > 1) {
            sql_error(&@2, yyscanner, parser, "wrong terminator.");
            return -1;
        }
        $$ = field_item;
    }
    | ENCLOSED BY STRING_LIT
    {
        FieldItem* field_item = new_node(FieldItem);
        field_item->location = parser->get_location(@1, @$);
        field_item->type = cantor::pb::ast::LOAD_ENCLOSED;
        field_item->value = ((ConstantExpr*)$3)->_u.str_val;;
        if (field_item->value.to_string()  != "\\" && field_item->value.length > 1) {
            sql_error(&@2, yyscanner, parser, "wrong terminator.");
            return -1;
        }
        $$ = field_item;
    }
    | ESCAPED BY STRING_LIT
    {
        FieldItem* field_item = new_node(FieldItem);
        field_item->location = parser->get_location(@1, @$);
        field_item->type = cantor::pb::ast::LOAD_ESCAPED;
        field_item->value = ((ConstantExpr*)$3)->_u.str_val;;
        if (field_item->value.to_string() != "\\" && field_item->value.length > 1) {
            sql_error(&@2, yyscanner, parser, "wrong terminator.");
            return -1;
        }
        $$ = field_item;
    }
    ;

Lines:
    {
        LinesClause* lines = new_node(LinesClause);
        lines->terminated = "\n";
        lines->starting = nullptr;
        $$ = lines;
    }
    |    LINES Starting LinesTerminated
    {
        LinesClause* lines = new_node(LinesClause);
        lines->location = parser->get_location(@1, @$);
        lines->starting = $2;
        lines->terminated = $3;
        $$ = lines;
    }
    ;

Starting:
    {
        $$ = "";
    }
    | STARTING BY STRING_LIT
    {
        $$ = ((ConstantExpr*)$3)->_u.str_val;
    }
    ;

LinesTerminated:
    {
        $$ = "\n";
    }
    | TERMINATED BY STRING_LIT
    {
        $$ = ((ConstantExpr*)$3)->_u.str_val;;
    }
    ;

ColumnNameOrUserVarListOptWithBrackets:
    {
        $$ = nullptr;
    }
    | '(' ColumnNameListOpt ')'
    {
        $$ = $2;
    }
    ;

LoadDataSetSpecOpt:
    {
        $$ = nullptr;
    }
    | SET AssignmentList
    {
        $$ = $2;
    }
    ;

ExplainableStmt:
    SelectStmt
    | UnionStmt 
    | DeleteStmt
    | UpdateStmt
    | InsertStmt
    | ReplaceStmt
    | LoadDataStmt
    ;

/************************************************************************************
 *  Account Management Statements
 *  https://dev.mysql.com/doc/refman/5.7/en/account-management-statements.html
 ************************************************************************************/
CreateNamespaceStmt:
    CREATE NAMESPACE IfNotExists NSName NamespaceOptionListOpt
    {
        CreateNamespaceStmt* stmt = new_node(CreateNamespaceStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_not_exists = $3;
        stmt->ns_name = $4;
        set_vector(stmt->options, $5);
        $$ = stmt;
    }
    ;
  
DropNamespaceStmt:
    DROP NAMESPACE IfExists NSName
    {
        DropNamespaceStmt* stmt = new_node(DropNamespaceStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exists = $3;
        stmt->ns_name = $4;
        $$ = stmt;
    }
    ;
    
AlterNamespaceStmt:
    ALTER NAMESPACE IfExists NSName NamespaceOptionListOpt
    {
        AlterNamespaceStmt* stmt = new_node(AlterNamespaceStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exists = $3;
        stmt->ns_name = $4;
        set_vector(stmt->options, $5);
        $$ = stmt;
    }
    ;

NSName:
    AllIdent {
        $$ = $1;
    }
    ;

NamespaceOption:
    AllIdent EqOpt Expr
    {
        NamespaceOption* option = new_node(NamespaceOption);
        option->location = parser->get_location(@1, @$);
        if ($1.to_lower() == "quota") {
            option->type = cantor::pb::ast::NAMESPACE_OPT_QUOTA;
            option->uint_value = ((ConstantExpr*)$3)->_u.int64_val;
        } else if ($1.to_lower() == "resource_tag") {
            option->type = cantor::pb::ast::NAMESPACE_OPT_RESOURCE_TAG;
            option->str_value = ((ConstantExpr*)$3)->_u.str_val;
        }
        $$ = option;
    }
    ;

NamespaceOptionListOpt:
    {
        $$ = nullptr;
    }
    | NamespaceOptionList
    {
        $$ = $1;
    }
    ;

NamespaceOptionList:
    NamespaceOption
    {
        $$ = append_vector<NamespaceOption*>(nullptr, $1, parser->arena);
    }
    | NamespaceOptionList NamespaceOption
    {
        $$ = append_vector<NamespaceOption*>($1, $2, parser->arena);
    }
    | NamespaceOptionList ',' NamespaceOption
    {
        $$ = append_vector<NamespaceOption*>($1, $3, parser->arena);
    }
    ;

CreateUserStmt:
    CREATE USER IfNotExists UserSpecList NamespaceOpt
    {
        // See https://dev.mysql.com/doc/refman/5.7/en/create-user.html
        CreateUserStmt* stmt = new_node(CreateUserStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_not_exists = $3;
        set_vector(stmt->specs, $4);
        stmt->namespace_name = $5;
        $$ = stmt;
    }
    ;
    
DropUserStmt:
    DROP USER IfExists UserSpecList
    {
        DropUserStmt* stmt = new_node(DropUserStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exists = $3;
        set_vector(stmt->specs, $4);
        $$ = stmt;
    }
    ;
    
AlterUserStmt:
    ALTER USER IfExists UserSpecList NamespaceOpt
    {
        AlterUserStmt* stmt = new_node(AlterUserStmt);
        stmt->location = parser->get_location(@1, @$);
        stmt->if_exists = $3;
        set_vector(stmt->specs, $4);
        stmt->namespace_name = $5;
        $$ = stmt;
    }
    ;

UserSpecList:
    UserSpec
    {
        $$ = append_vector<UserSpec*>(nullptr, $1, parser->arena);
    }
    |   UserSpecList ',' UserSpec
    {
        if ($3 != nullptr) {
            append_vector<UserSpec*>($1, $3, parser->arena);
        }
        $$ = $1;
    }
    ;

UserSpec:
    Username AuthOption
    {
        UserSpec* user_spec = new_node(UserSpec);
        user_spec->location = parser->get_location(@1, @$);
        user_spec->user = (UserIdentity*)$1;
        if ($2 != nullptr) {
            user_spec->auth_opt = $2;
        }
        $$ = user_spec;
    }
    ;

Username:
    StringName
    {
        UserIdentity* user = new_node(UserIdentity);
        user->location = parser->get_location(@1);
        user->username = $1;
        user->hostname = "%";
        $$ = user;
    }
    |   StringName '@' StringName
    {
        UserIdentity* user = new_node(UserIdentity);
        user->location = parser->get_location(@1, @$);
        user->username = $1;
        user->hostname = $3;
        $$ = user;
    }
    |   CURRENT_USER OptionalBraces
    {
        UserIdentity* user = new_node(UserIdentity);
        user->location = parser->get_location(@1, @$);
        user->current_user = true;
        $$ = user;
    }
    ;

OptionalBraces:
    {}
    |   '(' ')'
    {}

AuthOption:
    {
        $$ = nullptr;
    }
    |   IDENTIFIED BY AuthString
    {
        auto* auth_option = new_node(AuthOption);
        auth_option->location = parser->get_location(@1, @$);
        auth_option->auth_string = $3;
        $$ = auth_option;
    }
    ;

AuthString:
    STRING_LIT
    {
        $$ = ((ConstantExpr*)$1)->_u.str_val;
    }
    ;

NamespaceOpt:
    {
        $$ = nullptr;
    }
    |   NAMESPACE NSName
    {
        $$ = $2;
    }
    ;
    
GrantStmt:
    GRANT PrivTypeList ON PrivLevel TO UserSpecList WithGrantOptionOpt NamespaceOpt
    {
        GrantStmt* stmt = new_node(GrantStmt);
        stmt->location = parser->get_location(@1, @$);
        set_vector(stmt->privs, $2);
        stmt->priv_level = (PrivLevel*)$4;
        set_vector(stmt->specs, $6);
        stmt->with_grant = $7;
        stmt->namespace_name = $8;
        $$ = stmt;
    }
    ;

RevokeStmt:
    REVOKE PrivTypeList ON PrivLevel FROM UserSpecList NamespaceOpt
    {
        RevokeStmt* stmt = new_node(RevokeStmt);
        stmt->location = parser->get_location(@1, @$);
        set_vector(stmt->privs, $2);
        stmt->priv_level = (PrivLevel*)$4;
        set_vector(stmt->specs, $6);
        stmt->namespace_name = $7;
        $$ = stmt;
    }
    ;


PrivTypeList:
    PrivType
    {
        $$ = append_vector<PrivType*>(nullptr, $1, parser->arena);
    }
    |   PrivTypeList ',' PrivType
    {
        if ($3 != nullptr) {
            append_vector<PrivType*>($1, $3, parser->arena);
        }
        $$ = $1;
    }
    ;

PrivType:
    SqlACL {
        PrivType* priv = new_node(PrivType);
        priv->location = parser->get_location(@1);
        priv->type = (SqlACL)$1;
        $$ = priv;
    }
    ;

SqlACL:
	ALL {
		$$ = ALL_ACL;
	}
	|	ALL PRIVILEGES {
		$$ = ALL_ACL;
	}
	|	ALTER {
		$$ = ALTER_ACL;
	}
	|	ALTER ROUTINE {
		$$ = ALTER_PROC_ACL;
	}
	|	CREATE
	{
		$$ = CREATE_ACL;
	}
	|	CREATE ROUTINE {
		$$ = CREATE_PROC_ACL;
	}
	|	CREATE TABLESPACE {
		$$ = CREATE_TABLESPACE_ACL;
	}
	|	CREATE TEMPORARY TABLES {
		$$ = CREATE_TMP_ACL;
	}
	|	CREATE USER	{
		$$ = CREATE_USER_ACL;
	}
	|	CREATE VIEW {
		$$ = CREATE_VIEW_ACL;
	}
	|	DELETE {
		$$ = DELETE_ACL;
	}
	|	DROP {
		$$ = DROP_ACL;
	}
	|	EVENT {
		$$ = EVENT_ACL;
	}
	|	EXECUTE {
		$$ = EXECUTE_ACL;
	}
	|	FILE_T {
		$$ = FILE_ACL;
	}
	|	GRANT OPTION {
		$$ = GRANT_ACL;
	}
	|	INDEX {
		$$ = INDEX_ACL;
	}
	|	INSERT {
		$$ = INSERT_ACL;
	}
	|	LOCK TABLES {
		$$ = LOCK_TABLES_ACL;
	}
	|	PROCESS {
		$$ = PROCESS_ACL;
	}
	|	PROXY {
		$$ = PROXY_ACL;
	}
	|	REFERENCES {
		$$ = REFERENCES_ACL;
	}
	|	RELOAD {
		$$ = RELOAD_ACL;
	}
	|	REPLICATION CLIENT {
		$$ = REPL_CLIENT_ACL;
	}
	|	REPLICATION SLAVE {
		$$ = REPL_SLAVE_ACL;
	}
	|	SELECT {
		$$ = SELECT_ACL;
	}
	|	SHOW DATABASES {
		$$ = SHOW_DB_ACL;
	}
	|	SHOW VIEW {
		$$ = SHOW_VIEW_ACL;
	}
	|	SHUTDOWN {
		$$ = SHUTDOWN_ACL;
	}
	|	SUPER {
		$$ = SUPER_ACL;
	}
	|	TRIGGER	{
		$$ = TRIGGER_ACL;
	}
	|	UPDATE {
		$$ = UPDATE_ACL;
	}
	|	USAGE {
		$$ = NO_ACCESS_ACL;
	}
	;

PrivLevel:
    '*' {
        PrivLevel* priv_level = new_node(PrivLevel);
        priv_level->location = parser->get_location(@1);
        priv_level->level = cantor::pb::ast::GRANT_LEVEL_DB;
        $$ = priv_level;
    }
    | '*' '.' '*' {
        PrivLevel* priv_level = new_node(PrivLevel);
        priv_level->location = parser->get_location(@1, @$);
        priv_level->level = cantor::pb::ast::GRANT_LEVEL_GLOBAL;
        $$ = priv_level;
    }
    | AllIdent '.' '*' {
        PrivLevel* priv_level = new_node(PrivLevel);
        priv_level->location = parser->get_location(@1, @$);
        priv_level->level = cantor::pb::ast::GRANT_LEVEL_DB;
        priv_level->db_name = $1;
        $$ = priv_level;
    }
    | AllIdent '.' AllIdent {
        PrivLevel* priv_level = new_node(PrivLevel);
        priv_level->location = parser->get_location(@1, @$);
        priv_level->level = cantor::pb::ast::GRANT_LEVEL_TABLE;
        priv_level->db_name = $1;
        priv_level->table_name = $3;
        $$ = priv_level;
    }
    | AllIdent {
        PrivLevel* priv_level = new_node(PrivLevel);
        priv_level->location = parser->get_location(@1);
        priv_level->level = cantor::pb::ast::GRANT_LEVEL_TABLE;
        priv_level->table_name = $1;
        $$ = priv_level;
    }
    ;

WithGrantOptionOpt:
    {
        $$ = false;
    }
    | WITH GRANT OPTION {
        $$ = true;
    }
    ;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// TypeStmt syntax
///		SQL introduces a large amount of type-specific syntax.
///		Define individual clauses to handle these cases, and use
///		 the generic case to handle regular type-extensible Postgres syntax.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
opt_array_bounds:
			opt_array_bounds '[' ']'
			{
			    auto expr = ConstantExpr::make_int("-1", parser->arena);
			    expr->location = parser->get_location(@2, @3);
			    $$ = append_vector($1, expr, parser->arena);
            }
			| opt_array_bounds '[' INTEGER_LIT ']'
			{
			    $3->location = parser->get_location(@3);
			    $$ = append_vector($1, $3, parser->arena);
            }
			| /*EMPTY*/
			{
			    $$ = nullptr;
			}
		;

TypeStmt:
        SimpleTypename opt_array_bounds
		{
			set_vector($1->array_bounds, $2);
			$1->location = parser->get_location(@1, @$);
			$$ = $1;
		}
		| MAP '(' TypeStmt ',' TypeStmt ')' opt_array_bounds
		{
           auto type = TypeNameMap::make_typename(parser->arena);
           set_vector(type->array_bounds, $7);
           type->key = $3;
           type->value = $5;
           type->location = parser->get_location(@1, @$);
           $$ = type;
        }
        | SET '(' TypeStmt ')' opt_array_bounds
        {
            auto type = TypeNameSet::make_typename(parser->arena);
            set_vector(type->array_bounds, $5);
            type->key = $3;
            type->location = parser->get_location(@1, @$);
            $$ = type;

        }
        | TUPLE '(' type_list ')' opt_array_bounds
        {
           auto type = TypeNameTuple::make_typename(parser->arena);
           set_vector(type->array_bounds, $5);
           set_vector(type->type_args, $3);
           type->location = parser->get_location(@1, @$);
           $$ = type;
        }
        | UNION '(' colid_type_list ')' opt_array_bounds
        {
           auto type = TypeNameUnion::make_typename(parser->arena);
           set_vector(type->array_bounds, $5);
           set_vector(type->type_args, $3);
           type->location = parser->get_location(@1, @$);
           $$ = type;
        }
        | STRUCT '(' colid_type_list ')' opt_array_bounds
        {
           auto type = TypeNameStruct::make_typename(parser->arena);
           set_vector(type->array_bounds, $5);
           set_vector(type->type_args, $3);
           type->location = parser->get_location(@1, @$);
           $$ = type;
        }
        | PROTO '(' STRING_LIT ')' opt_array_bounds
        {
           auto type = TypeNameProto::make_typename(parser->arena);
           set_vector(type->array_bounds, $5);
           type->uri = $3;
           type->location = parser->get_location(@1, @$);
           $$ = type;
        }
        | ENUM TypeNameUri '{' EnumElementListOptComma '}'
        {
            auto type = TypeNameEnum::make_typename(parser->arena);
            type->uri = $2;
            set_vector(type->type_args, $4);
            type->location = parser->get_location(@1, @$);
            $$ = type;

        }
		;

ColId: 	AllIdent						{ $$ = $1; }

colid_type_list:
            ColId TypeStmt
            {
                auto node = new_node(NamedType);
                node->name = $1;
                node->type = $2;
                node->location = parser->get_location(@1, @$);
                $$ = append_vector<NamedType*>(nullptr, node, parser->arena);
            }
            | colid_type_list ',' ColId TypeStmt
            {
                auto node = new_node(NamedType);
                node->name = $3;
                node->type = $4;
                node->location = parser->get_location(@3, @$);
                $$ = append_vector<NamedType*>($1, node, parser->arena);
            }

type_list:	TypeStmt								{ $$ = append_vector<TypeStmt*>(nullptr, $1, parser->arena); }
			| type_list ',' TypeStmt				{ $$ = append_vector<TypeStmt*>($1, $3, parser->arena ); }
		;

SimpleTypename:
        NumericTypename
        {
            $$ = $1;
        }
        | DateAndTimeTypename
        {
            $$ = $1;
        }
        | CharacterType
        {
            $$ = $1;
        }
        | GenericType
        {
            $$ = $1;
        }
        ;

////////////////////////////////////////////
/// SQL numeric data types
////////////////////////////////////////////
NumericTypename:
        IntegerTypeStmt
        {
            $$ = $1;
        }
        | FloatTypeStmt
        {
            $$ = $1;
        }
        | BOOL
        {
            auto type = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_BOOL, parser->arena);
            type->location = parser->get_location(@1);
            $$ = type;
        }
        | DECIMAL opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_DECIMAL, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | NUMERIC opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_DECIMAL, parser->arena);
            set_vector($$->typmods,  $2);
            $$->location = parser->get_location(@1, @$);
        }
        ;

FloatTypeStmt:
        FP8 opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F8, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | FP16 opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F16, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | FP32 opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F32, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | FP64 opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F64, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | FLOAT opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F32, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | DOUBLE opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F64, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | DOUBLE PRECISION opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F64, parser->arena);
            set_vector(type->typmods, $3);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        | REAL opt_type_modifiers
        {
            auto type  = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_F64, parser->arena);
            set_vector(type->typmods, $2);
            type->location = parser->get_location(@1, @$);
            $$ = type;
        }
        ;

IntegerTypeStmt:
        INT opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INT8 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT8, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INT16 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT16, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INT32 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INT64 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT64, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INT128 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT128, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT8 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT8, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT16 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT16, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT32 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT64 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT64, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | UINT128 opt_type_modifiers
        {
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT128, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INTEGER opt_type_modifiers
        {
            /// 32 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | INTEGER opt_type_modifiers UNSIGNED
        {
            /// 32 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | TINYINT opt_type_modifiers
        {
            /// 8 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT8, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | TINYINT opt_type_modifiers UNSIGNED
        {
            /// 8 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT8, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | SMALLINT opt_type_modifiers
        {
            /// 16 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT16, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | SMALLINT opt_type_modifiers UNSIGNED
        {
            /// 16 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT16, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | MEDIUMINT opt_type_modifiers
        {
            /// 32 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | MEDIUMINT opt_type_modifiers UNSIGNED
        {
            /// 32 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT32, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | BIGINT opt_type_modifiers
        {
            /// 64 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_INT64, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        | BIGINT opt_type_modifiers UNSIGNED
        {
            /// 64 bits
            $$ = TypeNameNumeric::make_typename(cantor::pb::ast::NumericType::NT_UINT64, parser->arena);
            set_vector($$->typmods, $2);
            $$->location = parser->get_location(@1, @$);
        }
        ;

DateAndTimeTypename:
    DATETIME opt_type_modifiers
    {
        auto type = TypeNameDateTime::make_typename(parser->arena);
        set_vector($$->typmods, $2);
        type->precision = cantor::pb::ast::TimeStampPrecision::TS_NANO;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | DATETIME  DateTimePprecision opt_type_modifiers
    {
        auto type = TypeNameDateTime::make_typename(parser->arena);
        set_vector($$->typmods, $3);
        type->precision = $2;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | INTERVAL opt_type_modifiers
    {
        auto type = TypeNameInterval::make_typename( parser->arena);
        set_vector($$->typmods, $2);
        type->precision = cantor::pb::ast::TimeStampPrecision::TS_NANO;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | INTERVAL DateTimePprecision opt_type_modifiers
    {
        auto type = TypeNameInterval::make_typename( parser->arena);
        set_vector($$->typmods, $3);
         type->precision = $2;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | TIMESTAMP opt_type_modifiers
    {
        auto type = TypeNameTimestamp::make_typename( parser->arena);
        set_vector(type->typmods, $2);
        type->precision = cantor::pb::ast::TimeStampPrecision::TS_NANO;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | TIMESTAMP TimestampPprecision opt_type_modifiers
    {
        auto type = TypeNameTimestamp::make_typename( parser->arena);
        set_vector(type->typmods, $3);
        type->precision = $2;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | DURATION opt_type_modifiers
    {
        auto type = TypeNameDuration::make_typename( parser->arena);
        set_vector(type->typmods, $2);
        type->precision = cantor::pb::ast::TimeStampPrecision::TS_NANO;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    | DURATION TimestampPprecision opt_type_modifiers
    {
        auto type = TypeNameDuration::make_typename( parser->arena);
        set_vector(type->typmods, $3);
        type->precision = $2;
        type->location = parser->get_location(@1, @$);
        $$ = type;
    }
    ;

TimestampPprecision:
    NANOSECOND {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_NANO;
    }
    | MICROSECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_MICRO;
    }
    | MILLISECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_MILLI;
    }
    | SECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_SECOND;
    }
    ;
DateTimePprecision:
    NANOSECOND {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_NANO;
    }
    | MICROSECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_MICRO;
    }
    | MILLISECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_MILLI;
    }
    | SECOND
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_SECOND;
    }
    | MINUTE
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_MINUTE;
    }
    | HOUR
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_HOUR;
    }
    | DAY
    {
        $$ = cantor::pb::ast::TimeStampPrecision::TS_DAY;
    }
    ;

CharacterType:
    NATIONAL STRING opt_type_modifiers OptCharset OptCollate
    {
        auto type = TypeNameString::make_typename(false, parser->arena);
        set_vector(type->typmods, $3);
        type->charset = $4;
        type->collate = $5;
        type->location = parser->get_location(@1, @$);
        $$ = type;

    }
    | STRING opt_type_modifiers OptCharset OptCollate
    {
        auto type = TypeNameString::make_typename(false, parser->arena);
        set_vector(type->typmods, $2);
        type->charset = $3;
        type->collate = $4;
        type->location = parser->get_location(@1, @$);
        $$ = type;

    }
    | NATIONAL BINARY opt_type_modifiers OptCharset OptCollate
    {
        auto type = TypeNameString::make_typename(true, parser->arena);
        set_vector(type->typmods, $3);
        type->charset = $4;
        type->collate = $5;
        type->location = parser->get_location(@1, @$);
        $$ = type;

    }
    | BINARY opt_type_modifiers OptCharset OptCollate
    {
        auto type = TypeNameString::make_typename(true, parser->arena);
        set_vector(type->typmods, $2);
        type->charset = $3;
        type->collate = $4;
        type->location = parser->get_location(@1, @$);
        $$ = type;

    }
    | JSON opt_type_modifiers
    {
        auto type = TypeNameJson::make_typename(parser->arena);
        set_vector(type->typmods, $2);
        type->location = parser->get_location(@1);
        $$ = type;
    }
    | HLL opt_type_modifiers
    {
        auto type = TypeNameHll::make_typename(parser->arena);
        set_vector(type->typmods, $2);
        type->location = parser->get_location(@1);
        $$ = type;
    }
    | BITMAP opt_type_modifiers
    {
        auto type = TypeNameBitmap::make_typename(parser->arena);
        set_vector(type->typmods, $2);
        type->location = parser->get_location(@1);
        $$ = type;
    }
    | TDIGEST opt_type_modifiers
    {
        auto type = TypeNameTdigest::make_typename(parser->arena);
        set_vector(type->typmods, $2);
        type->location = parser->get_location(@1);
        $$ = type;
    }
    ;
/*
 * GenericType covers all type names that don't have special syntax mandated
 * by the standard, including qualified names.  We also allow type modifiers.
 * To avoid parsing conflicts against function invocations, the modifiers
 * have to be shown as expr_list here, but parse analysis will only accept
 * constants for them.
 */
GenericType:
			type_name_token opt_type_modifiers
            {
                auto type = TypeNameGeneric::make_typename($1, parser->arena);
                set_vector($$->typmods, $2);
                type->location = parser->get_location(@1, @$);
                $$ = type;
            }
            ;

type_name_token:
     IDENT {}
        ;

opt_type_modifiers: '(' opt_const_expr_list_opt_comma	 ')'
				{
				    $$ = $2;
				}
			    | /* EMPTY */
			    {
			        $$ = nullptr;
			    }
		        ;

opt_const_expr_list_opt_comma:
			const_expr_list_opt_comma
            {
                $$ = $1;
            }
			| /* empty */
            {
                $$ = nullptr;
            }
		    ;

const_expr_list_opt_comma:
			const_expr_list
            {
                $$ = $1;
            }
			|
			const_expr_list ','
            {
                $$ = $1;
            }
		    ;

const_expr_list:	const_expr
            {
                $$ = append_vector<ConstantExpr*>(nullptr, $1, parser->arena);
            }
			| const_expr_list ',' const_expr
            {
                $1->push_back($3, parser->arena);
                $$ = $1;
            }
		    ;

const_expr:
        INTEGER_LIT
        {
            $$->location = parser->get_location(@1);
            $$ = $1;
        }
        | STRING_LIT
        {
            $$->location = parser->get_location(@1);
            $$ = $1;
        }
        ;

MapPair:
       '{' Expr COLON Expr '}'
		{
		    auto node = new_node(MapPair);
		    node->key = $2;
		    node->value = $4;
		    node->location = parser->get_location(@1, @$);
		    $$ = node;
		}
        | Expr COLON Expr
        {
            auto node = new_node(MapPair);
            node->key = $1;
            node->value = $3;
            node->location = parser->get_location(@1, @$);
            $$ = node;
        }
	    ;
MapArgList:
			MapPair
			{
			    $$ = append_vector<MapPair*>(nullptr, $1, parser->arena);
			}
			| MapArgList ',' MapPair
			{
			    $1->push_back($3, parser->arena);
			    $$ = $1;
			}
	        ;
MapArgListOptComma:
			MapArgList							{ $$ = $1; }
			| MapArgList ','						{ $$ = $1; }
		    ;

OptMapArgListOptComma:
			MapArgListOptComma
			{
			    $$ = $1;
			}
			| /* empty */
			{
			    $$ = nullptr;
			}
            ;

DictPair:
	    Fieldkey COLON Expr
	    {
		    auto node = new_node(DictPair);
		    node->name = $1;
		    node->value = $3;
		    node->location = parser->get_location(@1, @$);
		    $$ = node;
	    }
	    ;
OptDictPair:
        { $$ = nullptr; }
        | DictPair
        {
            $$ = $1;
        }
DictPairList:
        DictPair
        {
            $$ = append_vector<DictPair*>(nullptr, $1, parser->arena);
        }
        | DictPairList ',' DictPair
        {
            $1->push_back($3, parser->arena);
            $$ = $1;
        }
        ;
DictPairListOptComma:
        DictPairList
        {
            $$ = $1;
        }
        | DictPairList ','
        {
             $$ = $1;
        }
        ;
OptDictPairListOptComma:
        DictPairListOptComma
        {
            $$ = $1;
        }
        |
        {
            $$ = nullptr;
        }
        ;


ArrayExprList:
        Expr
		{
		    $$ = append_vector<DispatchExpr*>(nullptr, $1, parser->arena);
		}
		| ArrayExprList ',' Expr
		{
		    $1->push_back($3, parser->arena);
		    $$ = $1;
		}
		;

ArrayExprListOptComma:
		ArrayExprList
		{
		    $$ = $1;
		}
		| ArrayExprList ','
		{
		    $$ = $1;
		}
		;

OptArrayExprListOptComma:
        ArrayExprListOptComma
        {
            $$ = $1;
        }
        | /* empty */
        {
            $$ = nullptr;
         }
        ;

Fieldkey:
        STRING_LIT
        {
            $$ = ((ConstantExpr*)$1)->_u.str_val;
        }
        | AllIdent
        {
            $$ = $1;
        }

EnumOpt:
    EQ_OP INTEGER_LIT
    {
        $$ = $2;
    }
    |
    {
        $$ = nullptr;
    }

EnumElement:
        Fieldkey EnumOpt
        {
             auto node = new_node(EnumElement);
             node->name = $1;
             node->value = $2;
             node->location = parser->get_location(@1, @$);
             $$ = node;
        }
        ;
EnumElementList:
        EnumElement
        {
             $$ = append_vector<EnumElement*>(nullptr, $1, parser->arena, 10);
        }
        | EnumElementList ',' EnumElement
        {
            $1->push_back($3, parser->arena);
            $$ = $1;
        }
        ;

EnumElementListOptComma:
        EnumElementList
        {
            $$ = $1;
        }
        | EnumElementList ','
        {
            $$ = $1;
        }
        ;

OptCollate:
    {
        $$ = nullptr;
    }
    | COLLATE StringName
    {
        $$ = $2;
    }
    ;

StringName:
    STRING_LIT
    {
        $$ = ((ConstantExpr*)$1)->_u.str_val;
    }
    | AllIdent
    {
        $$ = $1;
    }
    ;

OptCharset:
    {
        $$ = nullptr;
    }
    | CharsetKw StringName
    {
        $$ = $2;
    }
    ;

CreateTypeStmt:
        CREATE TYPE IfNotExists TypeScope TypeNameUri AS TypeStmt
        {
            auto node = new_node(CreateTypeStmt);
            node->uri = $5;
            node->type_name = $7;
            node->is_try = $3;
            node->scope_type = $4;
            node->location = parser->get_location(@1, @$);
            $$ = node;

        }
        | CREATE TYPE IfNotExists TypeScope TypeNameUri FROM PROTO STRING_LIT
        {
            auto node = new_node(CreateTypeStmt);
            node->uri = $5;
            node->proto = ((ConstantExpr*)$8)->_u.str_val.c_str();
            node->is_try = $3;
            node->scope_type = $4;
            node->location = parser->get_location(@1, @$);
            $$ = node;
        }
        | CREATE TYPE IfNotExists TypeScope TypeNameUri FROM JSON STRING_LIT
        {
            auto node = new_node(CreateTypeStmt);
            node->uri = $5;
            node->json = ((ConstantExpr*)$8)->_u.str_val.c_str();
            node->is_try = $3;
            node->scope_type = $4;
            node->location = parser->get_location(@1, @$);
            $$ = node;

        }
        ;
TypeScope:
    GLOBAL
    {
        $$ = cantor::pb::ast::TypeScope::TS_GLOBAL;
    }
    | NAMESPACE
    {
        $$ = cantor::pb::ast::TypeScope::TS_NAMESPACE;
    }
    | DATABASE
    {
        $$ = cantor::pb::ast::TypeScope::TS_DATABASE;
    }
    | TABLE
    {
        $$ = cantor::pb::ast::TypeScope::TS_TABLE;
    }
    |
    {
        $$ = cantor::pb::ast::TypeScope::TS_TABLE;
    }

//CreateUserStmt:
//    CREATE USER IfNotExists UserSpecList {
//        // See https://dev.mysql.com/doc/refman/5.7/en/create-user.html
//    }
//    ;
//
//UserSpecList:
//    UserSpec {
//    }
//    | UserSpecList ',' UserSpec {
//    }
//    ;
//
//UserSpec:
//    Username AuthOption {
//    }
//    ;
//
//Username:
//    STRING_LIT {
//    }
//    | STRING_LIT '@' STRING_LIT {
//    }
//    ;
//
//AuthOption:
//    {
//        $$ = nil;
//    }
//    | IDENTIFIED BY STRING_LIT {
//        $$ = $3;
//    }
//    | IDENTIFIED BY PASSWORD STRING_LIT {
//        $$ = $4;
//    }
//    ;

%%
int sql_error(YYLTYPE* yylloc, yyscan_t yyscanner, SqlParser *parser, const char *s) {    
    parser->error = cantor::parser::SYNTAX_ERROR;
    std::ostringstream os;
    auto loc = parser->get_location(*yylloc);
    os << s << ", near '" << std::string(yylloc->start, yylloc->end - yylloc->start);
    os << "' key:" << sql_get_text(yyscanner)<<", location:["<<loc.location<<":"<<loc.len<<"]";
    parser->syntax_err_str = os.str();
    return 1;
    //printf("sql_error");
}

